Skip to content

Commit

Permalink
Merge pull request #1375 from finos/fdc3-for-web-namespace-iframe-mes…
Browse files Browse the repository at this point in the history
…sages

Namespace (rename) iframe messages in FDC3 for the web to avoid potential conflicts
  • Loading branch information
kriswest authored Sep 25, 2024
2 parents f126aa4 + be131b6 commit eeb96f8
Show file tree
Hide file tree
Showing 21 changed files with 1,323 additions and 1,308 deletions.
83 changes: 42 additions & 41 deletions docs/api/ref/GetAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The `getAgent()` function is the recommended way for **web applications** to con
<TabItem value="ts" label="TypeScript">

```ts
import { getAgent, DesktopAgent } from "@finos/fdc3";
import { getAgent, DesktopAgent, AgentError } from "@finos/fdc3";

try {
const desktopAgent: DesktopAgent = await getAgent();
Expand Down Expand Up @@ -42,7 +42,7 @@ try {

The `getAgent()` function allows web applications to retrieve an FDC3 Desktop Agent API interface to work with, whether they are running in an environment that supports a Desktop Agent Preload (a container-injected API implementation) or a Desktop Agent Proxy (a Browser-based Desktop Agent running in another window or frame). The behavior of `getAgent()` is defined by the [FDC3 Web Connection Protocol (WCP)](../specs/webConnectionProtocol) and communication with a Desktop Agent Proxy in a web-browser is defined by the [Desktop Agent Communication Protocol (DACP)](../specs/desktopAgentCommunicationProtocol). Hence, it allows applications to be written that will work in either scenario without modification or the inclusion of vendor-specific libraries.

If no Desktop Agent is found, a failover function may be supplied by app allowing it to start or otherwise connect to a Desktop Agent (e.g. by loading a proprietary adaptor that returns a `DesktopAgent` implementation or by creating a window or iframe of its own that will provide a Desktop Agent Proxy).
To handle situations where no Desktop Agent is found, a failover function may be supplied by app allowing it to start or otherwise connect to a Desktop Agent (e.g. by loading a proprietary adaptor that returns a `DesktopAgent` implementation or by creating a window or iframe of its own that will provide a Desktop Agent Proxy).

The definition of the `getAgent()` function is as follows:

Expand All @@ -59,46 +59,48 @@ A small number of arguments are accepted that can affect the behavior of `getAge
* @typedef {Object} GetAgentParams Type representing parameters passed to the
* getAgent function.
*
* @property {string} identityUrl The app's current URL is normally sent to
* a web-based desktop agent to help establish its identity. This property
* may be used to override the URL sent (to handle situations where an app's
* URL is not sufficiently stable to use for identity purposes). The URL set
* MUST match the origin of the application (scheme, hostname, and port) or
* it will be ignored. If not specified, the app's current URL will be used.
* @property {number} timeoutMs Number of milliseconds to allow for an FDC3
* implementation to be found before calling the failover function or
* rejecting (default 750). Note that the timeout is cancelled as soon as a
* Desktop Agent is detected. There may be additional set-up steps to perform
* which will happen outside the timeout.
*
* @property {number} timeout Number of milliseconds to allow for an fdc3
* implementation to be found before calling the failover function or
* rejecting (default 750). Note that the timeout is cancelled as soon as a
* Desktop Agent is detected. There may be additional set-up steps to perform
* which will happen outside the timeout.
* @property {string} identityUrl The app's current URL is normally sent to
* a web-based desktop agent to help establish its identity. This property
* may be used to override the URL sent (to handle situations where an app's
* URL is not sufficiently stable to use for identity purposes, e.g. due to
* client-side route changes when navigating within the app). The URL set MUST
* match the origin of the application (scheme, hostname, and port) or it will
* be ignored. If not specified, the app's current URL will be used.
*
* @property {boolean} channelSelector Flag indicating that the application
* needs access to a channel selector UI (i.e. because it supports User Channels
* and does not implement its own UI for selecting channels). Defaults to true.
* MAY be ignored by Desktop Agent Preload (container) implementations.
* @property {boolean} channelSelector Flag indicating that the application
* needs access to a channel selector UI (i.e. because it supports User Channels
* and does not implement its own UI for selecting channels). Defaults to
* `true`. MAY be ignored by Desktop Agent Preload (container) implementations.
*
* @property {boolean} intentResolver Flag indicating that the application
* @property {boolean} intentResolver Flag indicating that the application
* needs access to an intent resolver UI (i.e. because it supports raising one
* or more intents and and does not implement its own UI for selecting target
* apps. Default to `true`. MAY be ignored by Desktop Agent Preload (container)
* or more intents and and does not implement its own UI for selecting target
* apps). Defaults to `true`. MAY be ignored by Desktop Agent Preload (container)
* implementations.
*
* @property {boolean} dontSetWindowFdc3 For backwards compatibility, `getAgent`
* will set a reference to the Desktop Agent implementation at `window.fdc3`
* if one does not already exist, and will fire the `fdc3Ready` event. Setting
* this flag to `true` will inhibit that behavior, leaving `window.fdc3` unset.
*
* @property {function} failover An optional function that provides a
* means of connecting to or starting a Desktop Agent, which will be called
* if no Desktop Agent is detected. Must return either a Desktop Agent
* implementation directly (e.g. by using a proprietary adaptor) or a
* WindowProxy (e.g a reference to another window returned by `window.open`
* or an iframe's `contentWindow`) for a window or frame in which it has loaded
* a Desktop Agent or suitable proxy to one that works with FDC3 Web Connection
* and Desktop Agent Communication Protocols.
* @property {boolean} dontSetWindowFdc3 For backwards compatibility, `getAgent`
* will set a reference to the Desktop Agent implementation at `window.fdc3`
* if one does not already exist, and will fire the fdc3Ready event. Defaults to
* `false`. Setting this flag to `true` will inhibit that behavior, leaving
* `window.fdc3` unset.
*
* @property {function} failover An optional function that provides a
* means of connecting to or starting a Desktop Agent, which will be called
* if no Desktop Agent is detected. Must return either a Desktop Agent
* implementation directly (e.g. by using a proprietary adaptor) or a
* WindowProxy (e.g a reference to another window returned by `window.open`
* or an iframe's `contentWindow`) for a window or frame in which it has loaded
* a Desktop Agent or suitable proxy to one that works with FDC3 Web Connection
* and Desktop Agent Communication Protocols.
*/
type GetAgentParams = {
timeout?: number,
timeoutMs?: number,
identityUrl?: string,
channelSelector?: boolean,
intentResolver?: boolean,
Expand All @@ -123,7 +125,6 @@ Example: Decreasing the timeout and providing a failover function

```js
const desktopAgent = await getAgent({
appId: "myApp@yourorg.org",
timeout: 250,
failover: async (params: GetAgentParams) => {
// return WindowProxy | DesktopAgent
Expand All @@ -135,7 +136,7 @@ The failover function allows an application to provide a backup mechanism for co

:::note

If you wish to _completely override FDC3s standard mechanisms_, then do not use a failover function. Instead, simply skip the `getAgent()` call and provide your own DesktopAgent object.
If you wish to _completely override FDC3's standard discovery mechanisms_, then do not use a failover function. Instead, simply skip the `getAgent()` call and provide your own DesktopAgent object.

:::

Expand Down Expand Up @@ -208,13 +209,13 @@ enum WebDesktopAgentType {

/** Denotes Desktop Agents that run (or provide an interface)
* within a parent window or frame, a reference to which
* will be found at `window.opener`, `window.parent` or
* `window.parent.opener`. */
* will be found at `window.opener`, `window.parent`,
* `window.parent.opener` etc. */
PROXY_PARENT = "PROXY_PARENT",

/** Denotes Desktop Agents that are connected to by loading
* a URL into a iframe whose URL was returned by a parent
* window or frame. */
/** Denotes Desktop Agents that are connected to by loading a URL
* into a hidden iframe whose URL was returned by a parent window
* or frame. */
PROXY_URL = "PROXY_URL",

/** Denotes a Desktop Agent that was returned by a failover
Expand Down
53 changes: 30 additions & 23 deletions docs/api/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Since version 1.2 of the FDC3 Standard it may do so via the [`fdc3.getInfo()`](r
<TabItem value="ts" label="TypeScript/JavaScript">

```ts
import {compareVersionNumbers, versionIsAtLeast} from '@finos/fdc3';
import { compareVersionNumbers, versionIsAtLeast } from "@finos/fdc3";

if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), "1.2")) {
await fdc3.raiseIntentForContext(context);
Expand All @@ -207,8 +207,8 @@ The [`ImplementationMetadata`](ref/Metadata#implementationmetadata) object retur
<TabItem value="ts" label="TypeScript/JavaScript">

```ts
let implementationMetadata = await fdc3.getInfo();
let {appId, instanceId} = implementationMetadata.appMetadata;
const implementationMetadata = await fdc3.getInfo();
const { appId, instanceId } = implementationMetadata.appMetadata;

```

Expand Down Expand Up @@ -274,26 +274,34 @@ await fdc3.raiseIntent("StartChat", context, appIntent.apps[0]);
const appIntent = await fdc3.findIntent("ViewContact", context, "fdc3.contact");
try {
const resolution = await fdc3.raiseIntent(appIntent.intent, context, appIntent.apps[0].name);
const result = await resolution.getResult();
console.log(`${resolution.source} returned ${JSON.stringify(result)}`);
} catch(error) {
console.error(`${resolution.source} returned a result error: ${error}`);
try {
const result = await resolution.getResult();
console.log(`${resolution.source} returned ${JSON.stringify(result)}`);
} catch(resultError: ResultError) {
console.error(`${resolution.source} returned an error: ${resultError.message}`);
}
} catch(resolveError: ResolveError) {
console.error(`${JSON.stringify(appIntent.apps[0])} returned an error: ${resolveError.message}`);
}

//Find apps to resolve an intent and return a channel
const appIntent = await fdc3.findIntent("QuoteStream", context, "channel");
try {
const resolution = await fdc3.raiseIntent(appIntent.intent, context, appIntent.apps[0].name);
const result = await resolution.getResult();
if (result && result.addContextListener) {
result.addContextListener(null, (context) => {
console.log(`received context: ${JSON.stringify(context)}`);
});
} else {
console.log(`${resolution.source} didn't return a channel! Result: ${JSON.stringify(result)}`);
try {
const result = await resolution.getResult();
if (result && result.addContextListener) {
result.addContextListener(null, (context) => {
console.log(`received context: ${JSON.stringify(context)}`);
});
} else {
console.log(`${resolution.source} didn't return a channel! Result: ${JSON.stringify(result)}`);
}
} catch(resultError: ResultError) {
console.error(`${resolution.source} returned an error: ${resultError.message}`);
}
} catch(error) {
console.error(`${resolution.source} returned a result error: ${error}`);
} catch (resolveError: ResolveError) {
console.error(`${JSON.stringify(appIntent.apps[0])} returned an error: ${resolveError.message}`);
}

//Find apps that can perform any intent with the specified context
Expand Down Expand Up @@ -383,7 +391,7 @@ For example, to raise a specific intent:
try {
const resolution = await fdc3.raiseIntent("StageOrder", context);
}
catch (err){ ... }
catch (err: ResolveError) { ... }
```

</TabItem>
Expand Down Expand Up @@ -411,8 +419,7 @@ try {
if (resolution.data) {
const orderId = resolution.data.id;
}
}
catch (err){ ... }
} catch (err: ResolveError) { ... }
```

</TabItem>
Expand Down Expand Up @@ -446,7 +453,7 @@ try {
//some time later
await agent.raiseIntent("UpdateOrder", context, resolution.source);
}
catch (err) { ... }
catch (err: ResolveError) { ... }
```

</TabItem>
Expand Down Expand Up @@ -478,13 +485,13 @@ try {
/* Detect whether the result is Context or a Channel by checking for properties unique to Channels. */
if (result && result.broadcast) {
console.log(`${resolution.source} returned a channel with id ${result.id}`);
} else if (result){
} else if (result) {
console.log(`${resolution.source} returned data: ${JSON.stringify(result)}`);
} else {
console.error(`${resolution.source} didn't return anything`);
}
} catch(error) {
console.error(`${resolution.source} returned a data error: ${error}`);
} catch(err: ResultError) {
console.error(`${resolution.source} returned a data error: ${err.message}`);
}
```

Expand Down
6 changes: 3 additions & 3 deletions docs/api/specs/browserResidentDesktopAgents.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,10 @@ User interface iframes are initially injected into the application window with C
and are always displayed with `position: "fixed"` so that they are not part of the document flow.
Implementations of the UIs may then indicate a limited set of CSS to apply to their frame in the initial `iFrameHello` message (when the width and height will be removed if not explicitly set in that message), and later adjust that via `iFrameRestyle`. See the [Controlling injected User Interfaces section](./desktopAgentCommunicationProtocol#controlling-injected-user-interfaces-section) in the DACP specification for more details.
Implementations of the UIs may then indicate a limited set of CSS to apply to their frame in the initial `Fdc3UserInterfaceHello` message (when the width and height will be removed if not explicitly set in that message), and later adjust that via `Fdc3UserInterfaceRestyle`. See the [Controlling injected User Interfaces section](./desktopAgentCommunicationProtocol#controlling-injected-user-interfaces-section) in the DACP specification for more details.
Communication between the `DesktopAgentProxy` and the iframes it injects is achieved via a similar mechanism to that used for communication between an App and the Desktop Agent: a `MessageChannel` is established between the app and iframe, via a `postMessage` sent from the iframe (`iFrameHello`) and responded to by the `DesktopAgentProxy` in the app's window (`iFrameHandshake`), with a `MessagePort` from a `MessageChannel` appended.
Communication between the `DesktopAgentProxy` and the iframes it injects is achieved via a similar mechanism to that used for communication between an App and the Desktop Agent: a `MessageChannel` is established between the app and iframe, via a `postMessage` sent from the iframe (`Fdc3UserInterfaceHello`) and responded to by the `DesktopAgentProxy` in the app's window (`Fdc3UserInterfaceHandshake`), with a `MessagePort` from a `MessageChannel` appended.
A further set of messages are provided for working with the injected user interfaces over their `MessageChannel` as part of the DACP, these are: `iFrameRestyle`, `iFrameDrag`, `iFrameChannels`, `iFrameChannelSelected`, `iFrameResolve` and `iFrameResolveAction`.
A further set of messages are provided for working with the injected user interfaces over their `MessageChannel` as part of the DACP, these are: `Fdc3UserInterfaceRestyle`, `Fdc3UserInterfaceDrag`, `Fdc3UserInterfaceChannels`, `Fdc3UserInterfaceChannelSelected`, `Fdc3UserInterfaceResolve` and `Fdc3UserInterfaceResolveAction`.
See the [Desktop Agent Communication Protocol](./desktopAgentCommunicationProtocol) (DACP) for more details.
Loading

0 comments on commit eeb96f8

Please sign in to comment.