Migrating Existing Apps to Platforms

🚧

Requirements

  • OpenFin CLI installed
  • OpenFin Runtime 15.80.49.30+
  • Windows Operating System | Mac not fully supported and may result in unexpected behavior

Overview

OpenFin Platforms makes creating a unified user experience for disparate web content easier than ever before. However, it represents a paradigm shift from traditional OpenFin applications. Transitioning existing OpenFin applications to an OpenFin Platform may be daunting. Below, we have resources and best practices to help you transition.

📘

Note - Deprecated OpenFin Layouts

If you have been using the now deprecated OpenFin Layouts, please see Migrating from Layouts V1 Workspaces to Platform Snapshots to help with migration to OpenFin Platform snapshots.

Key points

Existing OpenFin applications have generally been written with the assumption that they will be launched into an OpenFin window. In Platforms, the content will be loaded inside an OpenFin View. This results in the following challenges:

  • fin.Window.getCurrent: This call errors out when called from an OpenFin View. Existing OpenFin applications would likely require code changes in order to be launched into Platforms.
  • OpenFin Identity: The View will take on the UUID of the platform. Any bilateral communication or eventing using hardcoded UUID & name will no longer target the correct Identity.
  • fin.Window.wrap: Wrapping a View as a Window will not error out, but can present other challenges as the View and Window APIs are not 1:1. This can cause typeErrors and other unexpected behavior.
  • Custom Frame: The tab-set and common window-level UI involved in OpenFin Platforms will likely take on the functionality that may have previously existed in a custom top-bar frame in an OpenFin window. If the pre-existing application was using the OS frame, it will automatically be removed, and there should be no issue.

Example Solutions & Best Practices


fin.me

The fin namespace now contains a me property which represents a wrapped instance of the current execution context. You can also act on this object as you could any wrapped object returned by getCurrent or wrap calls. It also contains an entityType property and other helpers that help determine where your content is running.

Developers can write code that is portable from Window to View and vice-versa by using fin.me instead of any getCurrent calls. We encourage the use of fin.me whenever operating on the current execution context, for example when obtaining Identity for communication or for extracting metadata around the current execution context's state.

//Obtaining the current window 
let windowIdentity;
if (fin.me.isWindow) {
    windowIdentity = fin.me.identity;
} else if (fin.me.isView) {
    windowIdentity = (await fin.me.getCurrentWindow()).identity;
} else {
    throw new Error('Not running in a platform View or Window');
}

fin.Window.getCurrent

If this call is made from an OpenFin View, an error will be thrown which may be disruptive to the normal functioning of the web application.
In order to achieve the full benefits of OpenFin Platforms we recommend changes to pre-existing OpenFin applications, but we realize that this may not always be realistic or timely.

In order to minimize the disruption in content being launched into a View inside the Platform, a Platform provider may wish to overwrite fin.Window.getCurrent to return an object that changes each of the API methods to do one of four things:

  • Act on view
  • Act on view’s window
  • No-op
  • Other (create your own function or error out)

This likely will not be a holistic fix for the issue but can minimize the disruption caused by transitioning to Platforms.

See example.js file for an override map for each of the Window namespace APIs.

We believe this will help to minimize disruption.

We recommend that you test it out and modify it as you see fit.
// default get current override
const getCurrentWindowOverride = {
    addListener: 'view',
    addEventListener: 'view',
    animate: 'noop',
    authenticate: 'window',
    blur: 'noop',
    bringToFront: () => fin.View.getCurrentSync().focus(),
    center: 'noop',
    close: () => fin.Platform.getCurrentSync().closeView(fin.me.identity),
    disableUserMovement: 'noop',
    enableUserMovement: 'noop',
    executeJavaScript: 'view',
    findInPage: 'view',
    flash: 'noop',
    focus: 'view',
    getAllFrames: 'noop',
    getBounds: 'window',
    getCurrentViews: 'noop',
    getGroup: 'noop',
    getInfo: 'view',
    getNativeId: 'noop',
    getNativeWindow: 'noop',
    getOptions: 'view',
    getParentApplication: 'window',
    getParentWindow: 'noop',
    getPrinters: 'window',
    getSnapshot: 'noop',
    getState: 'window',
    getWebWindow: 'noop',
    getZoomLevel: 'view',
    hide: 'noop',
    isMainWindow: 'noop',
    isShowing: 'window',
    joinGroup: 'noop',
    leaveGroup: 'noop',
    maximize: 'noop',
    mergeGroups: 'noop',
    minimize: 'noop',
    moveBy: 'noop',
    moveTo: 'noop',
    navigate: 'view',
    navigateBack: 'view',
    navigateForward: 'view',
    on: 'view',
    once: 'view',
    prependListener: 'view',
    prependOnceListener: 'view',
    print: 'noop',
    reload: 'view',
    removeEventListener: 'view',
    removeAllListeners: 'view',
    removeListener: 'view',
    resizeBy: 'noop',
    resizeTo: 'noop',
    restore: 'noop',
    setAsForeground: () => fin.View.getCurrentSync().focus(),
    setBounds: 'noop',
    setZoomLevel: 'view',
    show: 'noop',
    showAt: 'noop',
    showDeveloperTools: 'view',
    stopFindInPage: 'view',
    stopFlashing: 'noop',
    stopNavigation: 'view',
    updateOptions: 'view',
};

Implementation

We recommend implementing this using a preload script that runs on the Views in which you wish to override fin.Window.getCurrent. You can ensure that this preload script runs in all views in the Platform using the defaultViewOptions property upon platform creation. Below is an example preload. Please ensure that this preload runs before any others that might contain fin.Window.getCurrent calls.

See example.js file for the logic to override `fin.Window.getCurrent`.

This assumes that the map shown in the code snippet above is in memory.

We recommend that you test it out and modify it as you see fit.
// ... paste above here a getCurrentWindowOverride map as shown in the code above
const noop = async () => {
    console.error("As part of running in a View inside Platforms, this API call has been no-oped.");
    return {};
};

// the View namespace only exists on the promise-based v2 API - change to callbacks for v1 override
const createV1Api = (api, replaceFunction) => {
    return async (...args) => {
        let hasCb = typeof args[args.length-1] === 'function';
        let hasErrorCb = hasCb && typeof args[args.length-2] === 'function';
        if (api === 'addEventListener') {
            // listener will be a fn so want to make sure we dont think its a cb...
            hasCb = typeof args[2] === 'function';
            hasErrorCb = typeof args[3] === 'function';
        }
        let returnValue;
        let errored = false;
        try {
            returnValue = await replaceFunction(...args);
            if(api === 'create-window') {
                const { uuid, name } = returnValue;
                returnValue = fin.desktop.Window.wrap(uuid, name);
            }
        } catch (e) {
            errored = true;
            if(hasErrorCb) {
                const errorCb = args[args.length-1];
                errorCb(e);
            }
        }
        if (hasCb && !errored) {
            const cb = hasErrorCb ? args[args.length-2] : args[args.length-1];
            // keep the cb out of the try catch so we don't accidentally call the errorCb
            cb(returnValue);
        }
    };
}

// setup global objects that will be overwritten as we obtain more information or the target window is changed 
const { uuid, name } = fin.__internal_.initialOptions;
let v2Window = fin.Window.wrapSync({uuid, name});
let v1Window = fin.desktop.Window.wrap(uuid, name);
const ofPlatform = fin.Platform.getCurrentSync();

// point the getCurrent APIs at these objects
fin.Window.getCurrent = async () => v2Window;
fin.Window.getCurrentSync = () => v2Window;
fin.desktop.Window.getCurrent = () => v1Window;

//Override create window to use platform API - will create a normal OpenFin window (not in a View)
fin.Window.create = async (...args) => {
    const identity = await ofPlatform.createWindow(...args);
    return fin.Window.wrap(identity);
};
fin.desktop.Window.create = createV1Api('create-window', ofPlatform.createWindow.bind(ofPlatform));

const overwriteGetCurrent = (windowIdentity, apiMap) => {
    const { uuid, name } = windowIdentity;
    v2Window = fin.Window.wrapSync({uuid, name});
    v1Window = fin.desktop.Window.wrap(uuid, name);
    v2View = fin.View.getCurrentSync();

    Object.entries(apiMap).forEach(([api, type]) => {
        if (type === 'noop') {
            v1Window[api] = createV1Api(api, noop);
            v2Window[api] = noop;
        } else if (type === 'view') {
            let replaceFunction = v2View[api] && v2View[api].bind(v2View);
            if (api === 'addEventListener') {
                replaceFunction = v2View.on.bind(v2View);
            } else if (api === 'removeEventListener') {
                replaceFunction = v2View.removeListener.bind(v2View);
            }
            v1Window[api] = createV1Api(api, replaceFunction);
            v2Window[api] = v2View[api];
        } else if (typeof type === 'function') {
            v1Window[api] = createV1Api(api, type);
            v2Window[api] = type;
        }
        // if type === 'window', we already have the correct function there
    });
};

if (fin.me.isView) {
    // run this synchronously to be sure to overwrite the APIs ASAP - may target the wrong window if target changed and reloaded
    overwriteGetCurrent(fin.__internal_.initialOptions.target, getCurrentWindowOverride);
    // initial Options target is only correct on initial load, might have reloaded or navigated the view - use this call to fix the APIs targeting the window
    fin.me.getCurrentWindow().then(ofWin => overwriteGetCurrent(ofWin.identity, getCurrentWindowOverride));
    // Any time the view changes window target, update the API overrides to point at the new Window
    fin.View.getCurrentSync().on('target-changed', ({target}) => overwriteGetCurrent(target, getCurrentWindowOverride));
}

Did this page help you?