Skip to content

Commit

Permalink
Add sample apps and update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
johnman committed Jun 18, 2024
1 parent 3c1bcec commit c37ac22
Show file tree
Hide file tree
Showing 27 changed files with 1,843 additions and 1,056 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Want to learn how to use our Channel API, Interop API or FDC3 API inside of a de
| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Web Interop Basic](./how-to/web-interop-basic) | This basic how-to provides a way of configuring a a webpage with a number of framed applications that share contextual information. | [Example](https://built-on-openfin.github.io/web-starter/web/v18.0.0/web-interop-basic/platform/provider.html) |
| [Web Interop](./how-to/web-interop) | This example how-to provides a way of configuring a a webpage with a number of framed applications using our layout system as well as a left hand panel so show that you can combine layout and iframe based content. | [Example](https://built-on-openfin.github.io/web-starter/web/v18.0.0/web-interop/platform/provider.html) |
| [Web Interop - Support Context and Intents](./how-to/web-interop-support-context-and-intents) | This is an example of a platform that has utilized the options of having a custom interop broker and a custom layout override to implement a platform that supports FDC3 as well as enhanced layout capabilities.. | [Example](https://built-on-openfin.github.io/web-starter/web/vnext/web-interop-support-context-and-intents/platform/provider.html) |

### Cloud Interop

Expand Down
244 changes: 83 additions & 161 deletions how-to/web-interop-support-context-and-intents/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
![OpenFin Web Interop Basic Example](../../assets/openfin-web-starter.png)
![OpenFin Support Context and Intents Example](../../assets/openfin-web-starter.png)

> **_:information_source: OpenFin:_** [OpenFin](https://www.openfin.co/) libraries are a commercial product and this repo is for evaluation purposes. Use of the OpenFin npm packages is only granted pursuant to a license from OpenFin. Please [**contact us**](https://www.openfin.co/contact/) if you would like to request a developer evaluation key or to discuss a production license.
# OpenFin Web Interop - Support Context and Intents
# OpenFin - Support Context and Intents

This is a simple example that has a simple provider web page that acts as the main/index page. This page wires up the interop broker, provides an interop override and creates a layout using the [@openfin/core-web](https://www.npmjs.com/package/@openfin/core-web) library.
This is a richer example of an implementation of an interop broker override and layout override showing to support an fdc3 app directory with support for context and intents.

This page has a very simple layout which is made up of four iframes that inherit the interop settings they should use to connect to the web broker:
It uses the [@openfin/core-web](https://www.npmjs.com/package/@openfin/core-web) library.

- Local - An FDC3 View - This uses the FDC3 API to add a context listener and to broadcast a hardcoded context object.
- Local - An Interop View - This uses the OpenFin Interop API to add a context listener and to set context using a hardcoded context object.
- External - An FDC3 Tool used in our workspace platform starters that lets you experiment with context sharing using the FDC3 APIs.
- External - An Interop Tool used in our workspace platform starters that lets you experiment with context sharing using the OpenFin Interop API.
The platform supports multiple layouts and brings in a number of OpenFin sample applications that are used in our workspace platform starter examples.

It also has a left panel which is outside of the OpenFin Layout and represents a platform specific panel which simply uses fdc3 and logs what it receives. This iframe does not inherit interop settings (as it is not part of the OpenFin layout) and uses platform specific settings to connect.
The platform also supports:

[Live Launch Example](https://built-on-openfin.github.io/web-starter/web/v18.0.0/web-interop/platform/provider.html)
## FDC3 Related

![OpenFin Web Interop Example](./docs/web-interop.png)
- Reading multiple fdc3 app directories endpoints to create a list of applications
- The ability to launch an app using fdc3.open
- The ability to raise intents or raise intents by context
- The ability to find instances of applications and target them when raising an intent.
- The ability to share context across content.
- The ability to share context across platforms and machines using our [@openfin/cloud-interop](https://www.npmjs.com/package/@openfin/cloud-interop) library

## Layout

To show so much content we implemented a layout override so that we can:

- Add and remove layouts
- Load a saved collection of layouts
- Switch the layout that is being viewed
- Finding the identity of a layout that contains a specific piece of content.
- Get the current snapshot of all the layouts and the currently focused layout.

## Apps

We bring in a number of apps from our workspace platform starter and dev tools. We also include 4 basic apps that support context and intents (using the fdc3 and Interop API).

[Live Launch Example](https://built-on-openfin.github.io/web-starter/web/vnext/web-interop-support-context-and-intents/platform/provider.html)

## Getting Started

Expand Down Expand Up @@ -53,143 +71,18 @@ There are a few things to note before trying to use @openfin/core-web:
- You will need to copy the shared-worker.js file from the [@openfin/core-web](https://www.npmjs.com/package/@openfin/core-web) npm package to your public folder. We have created a [copy-core-web.js](./scripts/copy-core-web.js) script to do this and it is referenced in the build-client npm command.
- You will need to copy the styles.css file for styling the layout from the [@openfin/core-web](https://www.npmjs.com/package/@openfin/core-web) npm package to your public folder. We have created a [copy-core-web.js](./scripts/copy-core-web.js) script to do this and it is referenced in the build-client npm command.

## How things are structured

### Host

The host is the entry point and it is the page that gets loaded into the Chrome/Edge/Safari/Firefox tab.

It has a responsibility to create a connection providing a broker url and then initializing the broker providing an id (**this id will be needed by your content when it wishes to connect**).

In the sample we use a [settings](./client/src/platform/settings.ts) file that reads settings from the [web manifest file](./public/manifest.json) but this has been removed from the snippet to simplify the code snippet.

```javascript
import { connect } from "@openfin/core-web";

/**
* Initializes the OpenFin Web Broker connection.
*/
async function init(): Promise<void> {
// Get the dom element that should host the layout
const layoutContainer = document.querySelector<HTMLElement>("#layout_container");

// Get the default layout
const layoutSnapshot = {...};

// Connect to the OpenFin Web Broker and pass the default layout.
// It is good practice to specify providerId even if content is explicitly specifying it for cases where
// this provider uses our layout system and content uses inheritance. currentContextGroup
// is useful for defaulting any client that uses inheritance through our layout system.
const fin = await connect({ options: {
brokerUrl: "http://localhost:6060/platform/iframe-broker.html",
interopConfig: {
providerId: "web-interop",
currentContextGroup: "green"
}
},
connectionInheritance: "enabled",
platform: { layoutSnapshot } });

// You may now use the `fin` object to initialize the broker and the layout.
await fin.Interop.init("web-interop");
// initialize the layout and pass it the dom element to bind to
await fin.Platform.Layout.init({
container: layoutContainer
});
}
```

The host html page [provider.html](./public/platform/provider.html) then:

- imports this code and initializes it.
- brings in required content through the @openfin/core-web layout system.
- brings in the required css for the @openfin/core-web layout system.

The host page initializes the OpenFin layout system and brings in a required css file that styles the layout system. This styles.css is brought in from the [@openfin/core-web](https://www.npmjs.com/package/@openfin/core-web) npm package. This style is copied to the public/style folder as core-web-styles.css using our [scripts/copy-core-web.js](./scripts/copy-core-web.js) script. It runs as part of the build process.

### IFrame Broker

This is the iframe that is referenced by the Host and Content Providers and it is how they communicate with each other. The iframe broker html page and the shared-webworker.js file have to reside on the same domain as the **host**.

The [iframe broker html page](./public/platform/iframe-broker.html) uses the shared-webworker.js file that comes as part of the [@openfin/core-web](https://www.npmjs.com/package/@openfin/core-web) npm package. This script is copied to the public/js folder as shared-worker.bundle.js using our [scripts/copy-core-web.js](./scripts/copy-core-web.js) script. It runs as part of the build process.

The iframe broker needs some initialization logic as well.

```javascript
import { init as initBrokerConnection } from "@openfin/core-web/iframe-broker";

/**
* Initializes the OpenFin Web Broker connection.
* @returns A promise that resolves when the connection is established.
*/
async function init(): Promise<void> {
// The shared worker is copied and renamed to the public/js directory from the @openfin/core-web package
// using the scripts/copy-shared-worker.js file that is called when npm run build is called.
return initBrokerConnection({
sharedWorkerUrl: "http://localhost:6060/js/shared-worker.bundle.js"
});
}
```

### Content

Content refers to content that is framed within an iframe on the **host** html page. It establishes a connection to the **host** through the **iframe broker** via some initialization code.

Some things to note about the content provider setup:

- Content imports an init function from the [api.ts](./client/src/platform/api.ts) that creates the connection and assigns the window.fin and window.fdc3 APIs if they do not exist.
- You do not need to assign fdc3 or fin to the window object but we have done so for consistency with our workspace and container starter examples.
- The snippet below is the init function from the [api.ts](./client/src/platform/api.ts) file (although the settings function has been replaced with hard coded values for simplicity) that is imported and called.
- Content initializes the API and then runs code normally like it would inside of a workspace platform or container platform.
- the **finReady** event shown below is an example and doesn't exist in the OpenFin container as the API is injected into the document. We added **finReady** to have similar behavior to the **fdc3Ready** event that we also raise.

```javascript
import { connect } from "@openfin/core-web";

/**
* Initializes the OpenFin Web Broker connection.
*/
export async function init(): Promise<void> {
// Set window.fin to the `fin` object if needed.
if (window.fin === undefined) {
// Specify an interopConfig with a specific provider ID and a context group to initialize the `fin.me.interop` client on connection.
window.fin = await connect({
options: {
brokerUrl: "http://localhost:6060/platform/iframe-broker.html",
interopConfig: {
providerId: "web-interop",
currentContextGroup: "green"
}
}
});
console.log("Finished initializing the fin API.");
// Create and dispatch the finReady event
const event = new CustomEvent("finReady");
window.dispatchEvent(event);
}

if (window.fdc3 === undefined && window?.fin?.me.interop?.getFDC3Sync !== undefined) {
window.fdc3 = fin.me.interop.getFDC3Sync("2.0");
console.log("Finished initializing the fdc3 API.");
// Create and dispatch the FDC3Ready event
const event = new CustomEvent("fdc3Ready");
window.dispatchEvent(event);
}
}
```
## Settings

To make it easier to update settings we store them in the web [manifest.json](./public/manifest.json) inside of _custom_settings_.
To make it easier to update settings we include a definition of where to fetch settings in our web [manifest.json](./public/manifest.json) inside of _custom_settings_.

```json
{
"name": "OpenFin Web Interop",
"short_name": "OpenFinWebInterop",
"name": "OpenFin - Support Context and Intents",
"short_name": "OpenFinSupportInteropContextAndIntents",
"start_url": "./platform/provider.html",
"display": "standalone",
"background_color": "#fff",
"description": "An example showing a implementation of the OpenFin Web Interop Library.",
"description": "An example showing a implementation of the OpenFin Core Web Library to support context and intents as well as layouts.",
"icons": [
{
"src": "common/images/icon-blue.png",
Expand All @@ -199,31 +92,60 @@ To make it easier to update settings we store them in the web [manifest.json](./
],
"related_applications": [],
"custom_settings": {
"platform": {
"interop": {
"sharedWorkerUrl": "http://localhost:6060/js/shared-worker.bundle.js",
"brokerUrl": "http://localhost:6060/platform/iframe-broker.html",
"providerId": "web-interop",
"defaultContextGroup": "green"
},
"layout": {
"panels": {
"left": {
"url": "http://localhost:6060/views/fdc3-panel.html",
"frameId": "left-panel"
"endpointProvider": {
"endpoints": [
{
"id": "platform-settings",
"type": "fetch",
"options": {
"method": "GET",
"url": "http://localhost:6060/settings.json"
}
},
"layoutContainerId": "layout_container",
"defaultLayout": "http://localhost:6060/layouts/default.layout.fin.json"
}
}
]
}
}
}
```

## A visual representation
We've covered the key pieces. We have a host, one or more pieces of content and a common iframe broker html page that is used to tie them altogether. We also use the OpenFin Layout system to render a layout that is compatible with the OpenFin container. This layout means that content doesn't need to know specific details about the host and it can inherit them. We also demonstrate content outside of the layout system by having a left panel example. This left panel specifies connection details.
As you can see it will fetch a [settings.json](./public/settings.json) file. The shape of this file can be seen in [client/src/shapes/setting-shapes.ts](./client/src/shapes/setting-shapes.ts) is as follows:

This diagram is here to provide a rough visual guide to support the content above and the example:
![OpenFin Web Interop Rough Visual Guide](./docs/web-interop-visualization.png)
```typescript
/**
* Settings for the client
*/
export interface Settings {
/**
* Platform settings
*/
platform: {
interop: {
sharedWorkerUrl: string;
brokerUrl: string;
providerId: string;
defaultContextGroup?: string;
overrideOptions: PlatformInteropBrokerOptions;
};
cloudInterop: {
connectParams: CloudInteropOverrideParams;
};
layout: {
addLayoutId: string;
deleteLayoutId: string;
layoutContainerId: string;
layoutSelectorId: string;
defaultLayout: PlatformLayoutSnapshot | string;
};
ui: {
logo: string;
title: string;
subTitle: string;
settingsResolver: SettingsResolverOptions;
};
app: {
directory: string;
appResolver: AppResolverOptions;
};
};
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { Context } from "@finos/fdc3";
import { init } from "./api";

window.addEventListener("DOMContentLoaded", async () => {
await init(true);
await initializeDOM();
});

/**
* Raise an intent using the fdc3 API.
*/
async function raiseIntent(): Promise<void> {
if (window.fin !== undefined) {
const context = {
type: "fdc3.contact",
name: "Andy Young",
id: {
email: "[email protected]"
}
};
const intent = "ViewContact";
const intentResolver = await window.fdc3.raiseIntent(intent, context);
if (intentResolver !== undefined) {
console.log("Intent resolver received:", intentResolver);
}
}
}

/**
* Adds an fdc3 intent listener to the window.
*/
async function addIntentListener(): Promise<void> {
const intent = "ViewContact";
if (window.fdc3) {
await window.fdc3.addIntentListener(intent, (ctx, metadata) => {
console.log(`Received Context For Intent: ${intent}`, ctx);
console.log(`Received Metadata With Intent: ${intent}`, metadata);
updateDOMElements(ctx);
});
} else {
window.addEventListener("fdc3Ready", async () => {
if (window.fdc3) {
await window.fdc3.addIntentListener(intent, (ctx, metadata) => {
console.log(`Received Context For Intent: ${intent}`, ctx);
console.log(`Received Metadata With Intent: ${intent}`, metadata);
updateDOMElements(ctx);
});
}
});
}
}

/**
* Updates the DOM elements with the provided context.
* @param context The context to update the DOM elements with.
*/
function updateDOMElements(context: Context): void {
const contextTypeSpan = document.querySelector<HTMLSpanElement>("#contextType");
const contextNameSpan = document.querySelector<HTMLSpanElement>("#contextName");
const contextBodyPre = document.querySelector<HTMLPreElement>("#contextBody");

if (contextTypeSpan !== null && contextNameSpan !== null && contextBodyPre !== null) {
contextTypeSpan.textContent = context.type;
contextNameSpan.textContent = context.name ?? "No name provided.";
contextBodyPre.textContent = JSON.stringify(context, null, 2);
}
}

/**
* Initialize the DOM elements.
*/
async function initializeDOM(): Promise<void> {
const raiseIntentButton = document.querySelector<HTMLButtonElement>("#raiseIntent");

if (raiseIntentButton !== null) {
raiseIntentButton.addEventListener("click", async () => {
await raiseIntent();
});
}

await addIntentListener();
}
Loading

0 comments on commit c37ac22

Please sign in to comment.