Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

450 allow intents to target app instances #509

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Added support for raiseIntent without a context via the addition of the `fdc3.nothing` context type ([#375](https://github.com/finos/FDC3/pull/375))
* Added [**FDC3 Workbench**](https://fdc3.finos.org/toolbox/fdc3-workbench/), an FDC3 API developer application ([#457](https://github.com/finos/FDC3/pull/457))
* Added advice on how to `broadcast` complex context types, composed of other types, so that other apps can listen for both the complex type and simpler constituent types ([#464](https://github.com/finos/FDC3/pull/464))
* Added an `instanceId` (and optional `instanceMetadata`) field to `AppMetadata` allowing it to refer to specific app instances and thereby supporting targetting of intents to specific app instances. Also added a `findInstanes()` function to the desktop agent. ([#509]((https://github.com/finos/FDC3/pull/509))

### Changed
* Consolidated `Listener` documentation with other types ([#404](https://github.com/finos/FDC3/pull/404))
Expand Down
128 changes: 86 additions & 42 deletions docs/api/ref/DesktopAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ It is expected that the `DesktopAgent` interface is made availabe via the [`wind
```ts
interface DesktopAgent {
// apps
open(app: TargetApp, context?: Context): Promise<void>;
open(app: TargetApp, context?: Context): Promise<AppMetadata>;
findInstances(app: TargetApp): Promise<Array<AppMetadata>>;

// context
broadcast(context: Context): void;
Expand Down Expand Up @@ -121,6 +122,27 @@ fdc3.broadcast(instrument);
#### See also
* [addContextListener](#addcontextlistener)

### `findInstances`

```ts
findInstances(app: TargetApp): Promise<Array<AppMetadata>>;
```

Find all the available instances for a particular application.

If there are no instances of the specified application the returned promise should resolve to an empty array.

If the resolution fails, the promise will return an `Error` with a string from the [`ResolveError`](ResolveError) enumeration.

### Example
```js
// Retrieve a list of instances of an application
let instances = await fdc3.findInstances({name: "MyApp"});

// Target a raised intent at a specific instance
let resolution = fdc3.raiseIntent("ViewInstrument", context, instances[0]);
```


### `findIntent`

Expand All @@ -131,8 +153,8 @@ findIntent(intent: string, context?: Context): Promise<AppIntent>;
Find out more information about a particular intent by passing its name, and optionally its context.

`findIntent` is effectively granting programmatic access to the Desktop Agent's resolver.
It returns a promise resolving to the intent, its metadata and metadata about the apps that are registered to handle it.
This can be used to raise the intent against a specific app.
It returns a promise resolving to the intent, its metadata and metadata about the apps and app instances that are registered to handle it.
This can be used to raise the intent against a specific app or app instance.

If the resolution fails, the promise will return an `Error` with a string from the [`ResolveError`](ResolveError) enumeration.

Expand All @@ -142,12 +164,29 @@ If the resolution fails, the promise will return an `Error` with a string from t
const appIntent = await fdc3.findIntent("StartChat");
// returns a single AppIntent:
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [{ name: "Skype" }, { name: "Symphony" }, { name: "Slack" }]
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [
// { name: "Skype" },
// { name: "Symphony" },
// { name: "Slack" }
// ]
// }

// raise the intent against a particular app
await fdc3.raiseIntent(appIntent.intent.name, context, appIntent.apps[0].name);

//later, we want to raise 'StartChat' intent again
const appIntent = await fdc3.findIntent("StartChat");
// returns an AppIntent, but with multiple options for resolution,
// which includes an existing instance of an application:
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [
// { name: "Skype" },
// { name: "Symphony" },
// { name: "Symphony", instanceId: "93d2fe3e-a66c-41e1-b80b-246b87120859" },
// { name: "Slack" }
// ]
```

#### See also
Expand All @@ -161,7 +200,7 @@ findIntentsByContext(context: Context): Promise<Array<AppIntent>>;

Find all the avalable intents for a particular context.
`findIntentsByContext` is effectively granting programmatic access to the Desktop Agent's resolver.
A promise resolving to all the intents, their metadata and metadata about the apps that registered as handlers is returned, based on the context types the intents have registered.
A promise resolving to all the intents, their metadata and metadata about the apps and app instances that registered as handlers is returned, based on the context types the intents have registered.

If the resolution fails, the promise will return an `Error` with a string from the [`ResolveError`](ResolveError) enumeration.

Expand All @@ -171,26 +210,34 @@ A promise resolving to all the intents, their metadata and metadata about the ap
const appIntents = await fdc3.findIntentsByContext(context);

// returns, for example:
// [{
// [
// {
// intent: { name: "StartCall", displayName: "Call" },
// apps: [{ name: "Skype" }]
// },
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [{ name: "Skype" }, { name: "Symphony" }, { name: "Slack" }]
// }];
// },
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [
// { name: "Skype" },
// { name: "Symphony" },
// { name: "Symphony", instanceId: "93d2fe3e-a66c-41e1-b80b-246b87120859" },
// { name: "Slack" }
// ]
// }
// ];

// select a particular intent to raise
const startChat = appIntents[1];

// target a particular app
const selectedApp = startChat.apps[0];
// target a particular app or instance
const selectedApp = startChat.apps[2];

// raise the intent, passing the given context, targeting the app
await fdc3.raiseIntent(startChat.intent.name, context, selectedApp.name);
await fdc3.raiseIntent(startChat.intent.name, context, selectedApp);
```

#### See also
* [`findIntent()`](#findintent)
* [`ResolveError`](Errors#resolveerror)


Expand Down Expand Up @@ -343,38 +390,38 @@ redChannel.addContextListener(null, channelListener);
### `open`

```ts
open(app: TargetApp, context?: Context): Promise<void>;
open(app: TargetApp, context?: Context): Promise<AppMetadata>;
```

Launches an app with target information, which can be either be a string like a name, or an [`AppMetadata`](Metadata#appmetadata) object.

The `open` method differs in use from [`raiseIntent`](#raiseIntent). Generally, it should be used when the target application is known but there is no specific intent. For example, if an application is querying the App Directory, `open` would be used to an app returned in the search results.
The `open` method differs in use from [`raiseIntent`](#raiseIntent). Generally, it should be used when the target application is known but there is no specific intent. For example, if an application is querying the App Directory, `open` would be used to open an app returned in the search results.

**Note**, if both the intent and target app name are known, it is recommended to instead use [`raiseIntent`](#raiseIntent) with the `app` argument.
**Note**, if the intent, context and target app name are all known, it is recommended to instead use [`raiseIntent`](#raiseIntent) with the `target` argument.

If a [`Context`](Types#context) object is passed in, this object will be provided to the opened application via a contextListener.
The Context argument is functionally equivalent to opening the target app with no context and broadcasting the context directly to it.
If a [`Context`](Types#context) object is passed in, this object will be provided to the opened application via a contextListener. The Context argument is functionally equivalent to opening the target app with no context and broadcasting the context directly to it.

Returns an [`AppMetadata`](Metadata#appmetadata) object with the `instanceId` field set identifying the instance of the application opened by this call.

If opening errors, it returns an `Error` with a string from the [`OpenError`](Errors#openerror) enumeration.

#### Example
```js
// no context
await fdc3.open('myApp');

// no context, with AppMetadata object as target
await fdc3.open({
name: 'myApp',
title: 'The title for the application myApp.',
description: '...'
});
// Open an app without context, using the app name
let instanceMetadata = await fdc3.open('myApp');

// Open an app without context, using an AppMetadata object to specify the target
let appMetadata = {name: 'myApp', appId: 'myApp-v1.0.1', version: '1.0.1'};
let instanceMetadata = await fdc3.open(appMetadata);

// with context
await fdc3.open('myApp', context);
// Open an app with context
let instanceMetadata = await fdc3.open(appMetadata, context);
```

#### See also
* [`Context`](Types#context)
* [`TargetApp`](Types#targetapp)
* [`AppMetadata`](Metadata#appmetadata)
* [`OpenError`](Errors#openerror)

### `raiseIntent`
Expand All @@ -385,29 +432,26 @@ raiseIntent(intent: string, context: Context, app?: TargetApp): Promise<IntentRe
Raises a specific intent for resolution against apps registered with the desktop agent.

The desktop agent will resolve the correct app to target based on the provided intent name and context data. If multiple matching apps are found, a method for resolving the intent to a target app, such as presenting the user with a resolver UI allowing them to pick an app, SHOULD be provided.
Alternatively, the specific app to target can also be provided. A list of valid target applications can be retrieved via [`findIntent`](DesktopAgent#findintent).
Alternatively, the specific app or app instance to target can also be provided. A list of valid target applications and instances can be retrieved via [`findIntent`](DesktopAgent#findintent).

If you wish to raise an Intent without a context, use the `fdc3.nothing` context type. This type exists so that apps can explicitly declare support for raising an intent without context.

Returns an `IntentResolution` object with details of the app that was selected to respond to the intent.
Returns an `IntentResolution` object with details of the app instance that was selected (or started) to respond to the intent.

If a target app for the intent cannot be found with the criteria provided, an `Error` with a string from the [`ResolveError`](Errors#resolverrror) enumeration is returned.
If a target app for the intent cannot be found with the criteria provided, an `Error` with a string from the [`ResolveError`](Errors#resolverrror) enumeration is returned. If a specific target `app` parameter was set, but either the app or app instance is not available then the `ResolveError.TargetAppUnavailable` or `ResolveError.TargetInstanceUnavailable` errors MUST be returned.

#### Example

```js
// raise an intent for resolution by the desktop agent
// a resolver UI may be displayed, or another method of resolving the intent to a
target applied, if more than one application can resolve the intent
// a resolver UI may be displayed, or another method of resolving the intent to a
// target applied, if more than one application can resolve the intent
await fdc3.raiseIntent("StartChat", context);

// or find apps to resolve an intent to start a chat with a given contact
const appIntent = await fdc3.findIntent("StartChat", context);

// use the name of one of the associated apps returned by findIntent as the specific intent target
await fdc3.raiseIntent("StartChat", context, appIntent.apps[0].name);

// or use the metadata of the app to fully describe the target app for the intent
// use the metadata of an app or app instance to describe the target app for the intent
await fdc3.raiseIntent("StartChat", context, appIntent.apps[0]);

//Raise an intent without a context by using the null context type
Expand All @@ -428,13 +472,13 @@ raiseIntentForContext(context: Context, app?: TargetApp): Promise<IntentResoluti
Finds and raises an intent against apps registered with the desktop agent based purely on the type of the context data.

The desktop agent SHOULD first resolve to a specific intent based on the provided context if more than one intent is available for the specified context. This MAY be achieved by displaying a resolver UI. It SHOULD then resolve to a specific app to handle the selected intent and specified context.
Alternatively, the specific app to target can also be provided, in which case any method of resolution SHOULD only consider intents supported by the specified application.
Alternatively, the specific app or app instance to target can also be provided, in which case any method of resolution SHOULD only consider intents supported by the specified application.

Using `raiseIntentForContext` is similar to calling `findIntentsByContext`, and then raising an intent against one of the returned apps, except in this case the desktop agent has the opportunity to provide the user with a richer selection interface where they can choose both the intent and target app.

Returns an `IntentResolution` object with a handle to the app that responded to the selected intent.

If a target app for the intent cannot be found with the criteria provided, an `Error` with a string from the [`ResolveError`](Errors#resolveerror) enumeration is returned.
If a target app or intent cannot be found with the criteria provided, an `Error` with a string from the [`ResolveError`](Errors#resolveerror) enumeration is returned. If a specific target `app` parameter was set, but either the app or app instance is not available then the `ResolveError.TargetAppUnavailable` or `ResolveError.TargetInstanceUnavailable` errors MUST be returned.

#### Example

Expand Down
14 changes: 9 additions & 5 deletions docs/api/ref/Errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ Contains constants representing the errors that can be encountered when calling
## `ResolveError`

```typescript
enum ResolveError {
NoAppsFound = "NoAppsFound",
ResolverUnavailable = "ResolverUnavailable",
ResolverTimeout = "ResolverTimeout"
export enum ResolveError {
NoAppsFound = 'NoAppsFound',
ResolverUnavailable = 'ResolverUnavailable',
ResolverTimeout = 'ResolverTimeout',
TargetAppUnavailable = 'TargetAppUnavailable',
TargetInstanceUnavailable = 'TargetInstanceUnavailable',
}
```

Contains constants representing the errors that can be encountered when calling the [`findIntent`](DesktopAgent#findintent) or [`findIntentsByContext`](DesktopAgent#findintentsbycontext) methods on the [DesktopAgent](DesktopAgent).
Contains constants representing the errors that can be encountered when calling the [`findIntent`](DesktopAgent#findintent), [`findIntentsByContext`](DesktopAgent#findintentsbycontext), [`raiseIntent`](DesktopAgent#raiseintent) or [`raiseIntentForContext`](DesktopAgent#raiseintentforcontext) methods on the [DesktopAgent](DesktopAgent).

#### See also
* [`DesktopAgent.findIntent`](DesktopAgent#findintent)
* [`DesktopAgent.findIntentsByContext`](DesktopAgent#findintentsbycontext)
* [`DesktopAgent.raiseIntent`](DesktopAgent#raiseintent)
* [`DesktopAgent.raiseIntentForContext`](DesktopAgent#raiseintentforcontext)

## `ChannelError`

Expand Down
80 changes: 61 additions & 19 deletions docs/api/ref/Metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,53 @@ For each intent, it reference the applications that support that intent.

```ts
interface AppMetadata {
name: string;
appId?: string;
version?: string;
title?: string;
tooltip?: string;
description?: string;
icons?: Array<Icon>;
images?: Array<string>;
/** The unique app name that can be used with the open and raiseIntent calls. */
readonly name: string;

/** The unique application identifier located within a specific application directory instance. An example of an appId might be 'app@sub.root' */
readonly appId?: string;

/** The Version of the application. */
readonly version?: string;

/** An optional instance identifier, indicating that this object represents a specific instance of the application described.*/
readonly instanceId?: string;

/** An optional set of, implementation specific, metadata fields that can be used to disambiguate instances, such as a window title or screen position. Must only be set if `instanceId` is set. */
readonly instanceMetadata?: Record<string, any>;

/** A more user-friendly application title that can be used to render UI elements */
readonly title?: string;

/** A tooltip for the application that can be used to render UI elements */
readonly tooltip?: string;

/** A longer, multi-paragraph description for the application that could include markup */
readonly description?: string;

/** A list of icon URLs for the application that can be used to render UI elements */
readonly icons?: Array<Icon>;

/** A list of image URLs for the application that can be used to render UI elements */
readonly images?: Array<string>;
}
```

App metadata is provided by the FDC3 App Directory that the desktop agent connects to.
Describes an application, or instance of an application, using metadata that is usually provided by an FDC3 App Directory that the desktop agent connects to.

It always includes at least a `name` property, which can be used with [`open`](DesktopAgent#open) and [`raiseIntent`](DesktopAgent#raiseIntent).
Will always includes at least a `name` property, which can be used with [`open`](DesktopAgent#open) and [`raiseIntent`](DesktopAgent#raiseIntent). If the `instanceId` field is set then the `AppMetadata` object represents a specific instance of the application that may be addressed using that Id.

Optionally, extra information from the app directory can be returned, to aid in rendering UI elements, e.g. a context menu.
This includes a title, description, tooltip and icon and image URLs.
Optionally, extra information from the app directory can be returned, to aid in rendering UI elements, e.g. a context menu. This includes a title, description, tooltip and icon and image URLs.

In situations where a desktop agent connects to multiple app directories or multiple versions of the same app exists in a single app directory, it may be neccessary to specify appId and version to target applications that share the same name.

`AppMetadata`

#### See also
* [`AppIntent.apps`](AppIntent)
* [`Icon`](Icon)
* [`DesktopAgent.findIntent`](DesktopAgent#findintent)
* [`DesktopAgent.raiseIntent`](DesktopAgent#raiseintent)

## `DisplayMetadata`

Expand Down Expand Up @@ -126,21 +150,39 @@ The Interface used to describe an Intent within the platform.

```ts
interface IntentResolution {
source: TargetApp;
/**
* Metadata about the app instance that was selected (or started) to resolve the intent.
* `source.instanceId` MUST be set, indicating the specific app instance that
* received the intent.
*/
readonly source: AppMetadata;
/**
* @deprecated not assignable from intent listeners
*/
data?: object;
version: string;
* @deprecated not assignable from intent listeners
*/
readonly data?: object;
/**
* The version number of the Intents schema being used.
*/
readonly version: string;
}
```

IntentResolution provides a standard format for data returned upon resolving an intent.

#### Example
#### Examples
```js
// resolve a "Chain" type intent
const intentResolution = await fdc3.raiseIntent("intentName", context);
let resolution = await fdc3.raiseIntent("intentName", context);

// Use metadata about the resolving app instance to target a further intent
try {
const resolution = await fdc3.raiseIntent('StageOrder', context);
...

//some time later
await agent.raiseIntent("UpdateOrder", context, resolution.source);
}
catch (err) { ... }
```

#### See also
Expand Down
Loading