diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e27fbf2..e4aaebef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,18 +22,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added a Trademarks page to website to acknowledge trademarks used within the Standard not owned by FINOS or the Linux Foundation ([#534](https://github.com/finos/FDC3/pull/534)) * Added details of FDC3's existing versioning and deprecation policies to the FDC3 compliance page ([#539](https://github.com/finos/FDC3/pull/539)) * Added a new experimental features policy, which exempts features designated as experimental from the versioning and deprecation policies, to the FDC3 compliance page ([#549](https://github.com/finos/FDC3/pull/549)) +* Added the current app's `AppMetadata` to the `ImplementationMetadata` returned by `fdc3.getInfo()` allowing an app to retrieve its own metadata, according to the Desktop Agent ([#726](https://github.com/finos/FDC3/pull/726)) * Added a context type representing a range of time (`fdc3.timerange`). ([#706](https://github.com/finos/FDC3/pull/706)) * Added a context type representing a Currency (`fdc3.currency`). ([#708](https://github.com/finos/FDC3/pull/708)) * Added a context type representing the price and value of a holding (`fdc3.valuation`). ([#709](https://github.com/finos/FDC3/pull/709)) * Added a context type representing a Chart (`fdc3.chart`). ([#715](https://github.com/finos/FDC3/pull/715)) -* Addition of `ViewProfile` and deprecation of `ViewContact` intents ([#619](https://github.com/finos/FDC3/pull/619)) -* Added a new context type `ChatInitSettings` to initialize a chat creation with new optional parameters ([#620](https://github.com/finos/FDC3/pull/620)) -* Added a `ViewResearch` Intent to be used when a user wants to see the latest research on a particular stock ([#623](https://github.com/finos/FDC3/pull/623)) +* Added a context type `ChatInitSettings` to initialize a chat creation with new optional parameters ([#620](https://github.com/finos/FDC3/pull/620)) * Added guide on how to submit a new Intent. ([#624](https://github.com/finos/FDC3/pull/624)) +* Added a `ViewResearch` Intent to be used when a user wants to see the latest research on a particular stock ([#623](https://github.com/finos/FDC3/pull/623)) +* Added a `ViewProfile` intent, which supersedes the `ViewContact` intent which is deprecated. ([#619](https://github.com/finos/FDC3/pull/619)) * Added a `ViewInteractions` intent to be used when a user wants to see the latest interactions (calls, meetings, conferences, roadshows) on a particular stock or with an individual or organization. ([#625](https://github.com/finos/FDC3/pull/625)) -* Added guide on how to submit a new Intent. ([#624](https://github.com/finos/FDC3/pull/624)) * Added a `ViewOrders` intent to be used when a user wants to see the order history of an individual, an institution or of a particular instrument. ([#672](https://github.com/finos/FDC3/pull/672)) -* Added a start `StartEmail` intent and `fdc3.email` context type to be used to initiate an email with a contact or list of contacts provided as part of the context. ([#632](https://github.com/finos/FDC3/pull/632)) +* Added a `StartEmail` intent and `fdc3.email` context type to be used to initiate an email with a contact or list of contacts provided as part of the context. ([#632](https://github.com/finos/FDC3/pull/632)) * Added a definition for "app directory record" to the FDC3 glossary to be used to refer to a single appD record ([#658](https://github.com/finos/FDC3/pull/658)) * Added `/v2/` paths to the AppD's specification, allowing a single implementation to support serving both FDC3 v1.2 and v2.0 application records, enabling simpler migration ([#666](https://github.com/finos/FDC3/pull/666)) * Added a `moreInfo` URL field to AppD application records to enable linking to a web page with more information on an app ([#669](https://github.com/finos/FDC3/pull/669)) diff --git a/docs/api/ref/DesktopAgent.md b/docs/api/ref/DesktopAgent.md index ba693f64b..81317c61c 100644 --- a/docs/api/ref/DesktopAgent.md +++ b/docs/api/ref/DesktopAgent.md @@ -204,6 +204,7 @@ 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`](Errors#resolveerror) enumeration. Result types may be a type name, the string `"channel"` (which indicates that the app will return a channel) or a string indicating a channel that returns a specific type, e.g. `"channel"`. If intent resolution to an app returning a channel is requested, the desktop agent MUST include both apps that are registered as returning a channel and those registered as returning a channel with a specific type in the response. + #### Examples I know 'StartChat' exists as a concept, and want to know which apps can resolve it: @@ -342,7 +343,7 @@ await fdc3.raiseIntent(startChat.intent.name, context, selectedApp); getCurrentChannel() : Promise; ``` -Optional function that returns the `Channel` object for the current User channel membership. In most cases, an application's membership of channels SHOULD be managed via UX provided to the application by the desktop agent, rather than calling this function directly. +Optional function that returns the `Channel` object for the current User channel membership. In most cases, an application's membership of channels SHOULD be managed via UX provided to the application by the desktop agent, rather than calling this function directly. Returns `null` if the app is not joined to a channel. @@ -357,15 +358,13 @@ let current = await fdc3.getCurrentChannel(); * [`Channel`](Channel) - - ### `getInfo` ```ts getInfo(): Promise; ``` -Retrieves information about the FDC3 Desktop Agent implementation, such as the implemented version of the FDC3 specification and the name of the implementation provider. +Retrieves information about the FDC3 Desktop Agent implementation, including the supported version of the FDC3 specification, the name of the provider of the implementation, its own version number and the metadata of the calling application according to the desktop agent. Returns an [`ImplementationMetadata`](Metadata#implementationmetadata) object. This metadata object can be used to vary the behavior of an application based on the version supported by the Desktop Agent and for logging purposes. @@ -381,9 +380,18 @@ if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), "1.2")) { } ``` +The `ImplementationMetadata` object returned also includes the metadata for the calling application, according to the Desktop Agent. This allows the application to retrieve its own `appId`, `instanceId` and other details, e.g.: + +```js +let implementationMetadata = await fdc3.getInfo(); +let {appId, instanceId} = implementationMetadata.appMetadata; + +``` + #### See also * [`ImplementationMetadata`](Metadata#implementationmetadata) +* [`AppMetadata`](Metadata#appmetadata) ### `getOrCreateChannel` @@ -469,7 +477,7 @@ fdc3.addIntentListener("QuoteStream", async (context) => { getUserChannels() : Promise>; ``` -Retrieves a list of the User Channels available for the app to join. +Retrieves a list of the User Channels available for the app to join. #### Example @@ -490,7 +498,7 @@ joinUserChannel(channelId: string) : Promise; Optional function that joins the app to the specified User channel. In most cases, applications SHOULD be joined to channels via UX provided to the application by the desktop agent, rather than calling this function directly. -If an app is joined to a channel, all `fdc3.broadcast` calls will go to the channel, and all listeners assigned via `fdc3.addContextListener` will listen on the channel. +If an app is joined to a channel, all `fdc3.broadcast` calls will go to the channel, and all listeners assigned via `fdc3.addContextListener` will listen on the channel. If the channel already contains context that would be passed to context listeners added via `fdc3.addContextListener` then those listeners will be called immediately with that context. @@ -510,6 +518,7 @@ const channels = await fdc3.getUserChannels(); fdc3.joinUserChannel(selectedChannel.id); ``` + #### See also * [`getUserChannels`](#getuserchannels) @@ -592,7 +601,7 @@ If a target app for the intent cannot be found with the criteria provided or the 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`](Metadata#intentresolution) object with details of the app instance that was selected (or started) to respond to the intent. +Returns an [`IntentResolution`](Metadata#intentresolution) object with details of the app instance that was selected (or started) to respond to the intent. Issuing apps may optionally wait on the promise that is returned by the `getResult()` member of the IntentResolution. This promise will resolve when the _receiving app's_ intent handler function returns and resolves a promise. The Desktop Agent resolves the issuing app's promise with the Context object or Channel that is provided as resolution by the receiving app. The Desktop Agent MUST reject the issuing app's promise, with a string from the [`ResultError`](Errors#resulterror) enumeration, if: (1) the intent handling function's returned promise rejects, (2) the intent handling function doesn't return a promise, or (3) the returned promise resolves to an invalid type. @@ -628,6 +637,7 @@ try { console.error(`${resolution.source} returned a result error: ${error}`); } ``` + #### See also * [`Context`](Types#context) @@ -645,14 +655,14 @@ raiseIntentForContext(context: Context, app?: AppIdentifier): Promise"`, e.g. `"channel"`. Requesting intent resolution to an app returning a channel MUST include apps that are registered as returning a channel with a specific type. +Result context types requested are represented by their type name. A channel may be requested by passing the string `"channel"` or a channel that returns a specific type via the syntax `"channel"`, e.g. `"channel"`. Requesting intent resolution to an app returning a channel MUST include apps that are registered as returning a channel with a specific type. ### Intent Resolution + Raising an intent will return a Promise-type object that will resolve/reject based on a number of factors. #### Resolve + - Intent was resolved unambiguously and the receiving app was launched successfully (if necessary). - Intent was ambiguous, a resolution was chosen by the end user, and the chosen application was launched successfully. #### Reject + - No app matching the intent and context (if specified) was found. - A match was found, but the receiving app failed to launch. - The intent was ambiguous and the resolver experienced an error. #### Resolution Object -If the raising of the intent resolves (or rejects), a standard [`IntentResolution`](ref/Metadata#intentresolution) object will be passed into the resolver function with details of the application that resolved the intent and the means to access any results subsequently returned. + +If the raising of the intent resolves (or rejects), a standard [`IntentResolution`](ref/Metadata#intentresolution) object will be passed into the resolver function with details of the application that resolved the intent and the means to access any results subsequently returned. For example, to raise a specific intent: @@ -228,6 +257,7 @@ catch (err){ ... } ``` Use metadata about the resolving app instance to target a further intent + ```js try { const resolution = await fdc3.raiseIntent('StageOrder', context); @@ -240,6 +270,7 @@ catch (err) { ... } ``` Raise an intent and retrieve either data or a channel from the IntentResolution: + ```js let resolution = await agent.raiseIntent("intentName", context); try { @@ -258,11 +289,13 @@ try { ``` ### Register an Intent Handler + Applications need to let the system know the intents they can support. Typically, this is done via registration with an [App Directory](../app-directory/spec). It is also possible for intents to be registered at the application level as well to support ad-hoc registration which may be helpful at development time. Although dynamic registration is not part of this specification, a Desktop Agent agent may choose to support any number of registration paths. -When an instance of an application is launched, it is expected to add an [`IntentHandler`](ref/Types#intenthandler) function to the desktop agent for each intent it has registered by calling the [`fdc3.addIntentListener`](ref/DesktopAgent#addintentlistener) function of the Desktop Agent. Doing so allows the Desktop Agent to pass incoming intents and contexts to that instance of the application. Hence, if the application instance was spawned in response to the raised intent, then the Desktop Agent must wait for the relevant intent listener to be added by that instance, before it can deliver the intent and context to it. In order to facilitate accurate error responses, calls to `fdc3.raiseIntent` should not return an `IntentResolution` until the intent handler has been added and the intent delivered to the target app. +When an instance of an application is launched, it is expected to add an [`IntentHandler`](ref/Types#intenthandler) function to the desktop agent for each intent it has registered by calling the [`fdc3.addIntentListener`](ref/DesktopAgent#addintentlistener) function of the Desktop Agent. Doing so allows the Desktop Agent to pass incoming intents and contexts to that instance of the application. Hence, if the application instance was spawned in response to the raised intent, then the Desktop Agent must wait for the relevant intent listener to be added by that instance before it can deliver the intent and context to it. In order to facilitate accurate error responses, calls to `fdc3.raiseIntent` should not return an `IntentResolution` until the intent handler has been added and the intent delivered to the target app. #### Compliance with Intent Standards + Intents represent a contract with expected behaviour if an app asserts that it supports the intent. Where this contract is enforceable by schema (for example, return object types), the FDC3 API implementation SHOULD enforce compliance and return an error if the interface is not met. It is expected that App Directories SHOULD also curate listed apps and ensure that they are complying with declared intents. @@ -272,35 +305,37 @@ It is expected that App Directories SHOULD also curate listed apps and ensure th Context channels allows a set of apps to share a stateful piece of data between them, and be alerted when it changes. Use cases for channels include color linking between applications to automate the sharing of context and topic based pub/sub such as theme. ### Types of Channel + There are three types of channels, which have different visibility and discoverability semantics: -1. **_User channels_**, which: - * facilitate the creation of user-controlled context links between applications (often via the selection of a color channel), - * are created and named by the desktop agent, - * are discoverable (via the [`getUserChannels()`](ref/DesktopAgent#getuserchannels) API call), - * can be 'joined' (via the [`joinUserChannel()`](ref/DesktopAgent#joinuserchannel) API call). +1. ***User channels***, which: + - facilitate the creation of user-controlled context links between applications (often via the selection of a color channel), + - are created and named by the desktop agent, + - are discoverable (via the [`getUserChannels()`](ref/DesktopAgent#getuserchannels) API call), + - can be 'joined' (via the [`joinUserChannel()`](ref/DesktopAgent#joinuserchannel) API call). > **Note:** Prior to FDC3 2.0, 'user' channels were known as 'system' channels. They were renamed in FDC3 2.0 to reflect their intended usage, rather than the fact that they are created by system (which could also create 'app' channels). > **Note:** Earlier versions of FDC3 included the concept of a 'global' system channel which was deprecated in FDC3 1.2 and removed in FDC3 2.0. -2. **_App channels_**, which: - * facilitate developer controlled messaging between applications, - * are created and named by applications (via the [`getOrCreateChannel()`](ref/DesktopAgent#getorcreatechannel) API call), - * are not discoverable, - * are interacted with via the [Channel API](ref/Channel) (accessed via the desktop agent [`getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel) API call) +2. ***App channels***, which: + - facilitate developer controlled messaging between applications, + - are created and named by applications (via the [`getOrCreateChannel()`](ref/DesktopAgent#getorcreatechannel) API call), + - are not discoverable, + - are interacted with via the [Channel API](ref/Channel) (accessed via the desktop agent [`getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel) API call) -3. **_Private_** channels, which: - * facilitate private communication between two parties, - * have an auto-generated identity and can only be retrieved via a raised intent. +3. ***Private*** channels, which: + - facilitate private communication between two parties, + - have an auto-generated identity and can only be retrieved via a raised intent. Channels are interacted with via `broadcast` and `addContextListener` functions, allowing an application to send and receive Context objects via the channel. For User channels, these functions are provided on the Desktop Agent, e.g. [`fdc3.broadcast(context)`](ref/DesktopAgent#broadcast), and apply to channels joined via [`fdc3.joinUserChannel`](ref/DesktopAgent#joinuserchannel). For App channels, a channel object must be retrieved, via [`fdc3.getOrCreateChannel(channelName)`](ref/DesktopAgent#getorcreatechannel), which provides the functions, i.e. [`myChannel.broadcast(context)`](ref/Channel#broadcast) and [`myChannel.addContextListener(context)`](ref/Channel#addcontextlistener). For `PrivateChannels`, a channel object must also be retrieved, but via an intent raised with [`fdc3.raiseIntent(intent, context)`](ref/DesktopAgent#raiseintent) and returned as an [`IntentResult`](ref/Types#intentresult). Channel implementations SHOULD ensure that context messages broadcast by an application on a channel are not delivered back to that same application if they are also listening on the channel. ### Joining User Channels -Apps can join _User channels_. An app can only be joined to one User channel at a time. + +Apps can join *User channels*. An app can only be joined to one User channel at a time. When an app is joined to a User channel, calls to [`fdc3.broadcast`](ref/DesktopAgent#broadcast) will be routed to that channel and listeners added through [`fdc3.addContextListener`](ref/DesktopAgent#addcontextlistener) will receive context broadcasts from other apps also joined to that channel. If an app is not joined to a User channel [`fdc3.broadcast`](ref/DesktopAgent#broadcast) will be a no-op and handler functions added with [`fdc3.addContextListener`](ref/DesktopAgent#addcontextlistener) will not receive any broadcasts. However, apps can still choose to listen and broadcast to specific channels (both User and App channels) via the methods on the [`Channel`](ref/Channel) class. @@ -308,11 +343,12 @@ When an app joins a User channel, or adds a context listener when already joined It is possible that a call to join a User channel could be rejected. If for example, the desktop agent wanted to implement controls around what data apps can access. -Joining channels in FDC3 is intended to be a behavior initiated by the end user. For example: by color linking or apps being grouped in the same workspace. Most of the time, it is expected that apps will be joined to a channel by mechanisms outside of the app. To support programmatic management of joined channels and the implementation of channel selector UIs other than those provided outside of the app, Desktop Agent implementations MAY provide [`fdc3.joinChannel()`](ref/DesktopAgent#joinchannel), [`fdc3.getCurrentChannel()](ref/DesktopAgent#getcurrentchannel) and [`fdc3.leaveCurrentChannel()`](ref/DesktopAgent#leavecurrentchannel) functions and if they do, MUST do so as defined in the [Desktop Agent API reference](ref/DesktopAgent). +Joining channels in FDC3 is intended to be a behavior initiated by the end user. For example: by color linking or apps being grouped in the same workspace. Most of the time, it is expected that apps will be joined to a channel by mechanisms outside of the app. To support programmatic management of joined channels and the implementation of channel selector UIs other than those provided outside of the app, Desktop Agent implementations MAY provide [`fdc3.joinChannel()`](ref/DesktopAgent#joinchannel), [`fdc3.getCurrentChannel()](ref/DesktopAgent#getcurrentchannel) and [`fdc3.leaveCurrentChannel()`](ref/DesktopAgent#leavecurrentchannel) functions and if they do, MUST do so as defined in the [Desktop Agent API reference](ref/DesktopAgent). There SHOULD always be a clear UX indicator of what channel an app is joined to. #### Examples + To find a User channel, one calls: ```js @@ -334,9 +370,11 @@ Channel implementations SHOULD ensure that context messages broadcast by an appl > Prior to FDC3 2.0, 'user' channels were known as 'system' channels. They were renamed in FDC 2.0 to reflect their intended usage, rather than the fact that they are created by system (which could also create 'app' channels). The `joinChannel` function was also renamed to `joinUserChannel` to clarify that it is only intended to be used to join 'user', rather than 'app', channels. ### Direct Listening and Broadcast on Channels -While joining User channels (using fdc3.joinUserChannel) automates a lot of the channel behaviour for an app, it has the limitation that an app can only be 'joined' to one channel at a time. However, an app may instead retrieve a `Channel` Object via the [`fdc3.getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel) API, or by raising an intent that returns a channel. The `Channel` object may then be used to listen to and broadcast on that channel directly using the [`Channel.addContextListener`](ref/Channel#addcontextlistener) and the [`Channel.broadcast`](ref/Channel#broadcast) APIs. This is especially useful for working with dynamic _App Channels_. FDC3 imposes no restriction on adding context listeners or broadcasting to multiple channels. + +While joining User channels (using fdc3.joinUserChannel) automates a lot of the channel behaviour for an app, it has the limitation that an app can only be 'joined' to one channel at a time. However, an app may instead retrieve a `Channel` Object via the [`fdc3.getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel) API, or by raising an intent that returns a channel. The `Channel` object may then be used to listen to and broadcast on that channel directly using the [`Channel.addContextListener`](ref/Channel#addcontextlistener) and the [`Channel.broadcast`](ref/Channel#broadcast) APIs. This is especially useful for working with dynamic *App Channels*. FDC3 imposes no restriction on adding context listeners or broadcasting to multiple channels. ### App Channels + App Channels are topics dynamically created by applications connected via FDC3. For example, an app may` create a channel to broadcast to others data or status specific to that app. To get (or create) a channel reference, then interact with it: @@ -374,17 +412,17 @@ if another application broadcasts to "my_custom_channel" (by retrieving it and b ### Private Channels -`PrivateChannels` are created to support the return of a stream of responses from a raised intent, or private dialog between two applications. +`PrivateChannels` are created to support the return of a stream of responses from a raised intent, or private dialog between two applications. It is intended that Desktop Agent implementations: - * - SHOULD restrict external apps from listening or publishing on this channel. - * - MUST prevent `PrivateChannels` from being retrieved via `fdc3.getOrCreateChannel`. - * - MUST provide the `id` value for the channel as required by the `Channel` interface. + - - SHOULD restrict external apps from listening or publishing on this channel. + - - MUST prevent `PrivateChannels` from being retrieved via `fdc3.getOrCreateChannel`. + - - MUST provide the `id` value for the channel as required by the `Channel` interface. The `PrivateChannel` type also supports synchronisation of data transmitted over returned channels. They do so by extending the `Channel` interface with event handlers which provide information on the connection state of both parties, ensuring that desktop agents do not need to queue or retain messages that are broadcast before a context listener is added and that applications are able to stop broadcasting messages when the other party has disconnected. ### Broadcasting and listening for multiple context types -The [Context specification](../../context/spec#assumptions) recommends that complex context objects are defined using simpler context types for particular fields. For example, a `Position` is composed of an `Instrument` and a holding amount. This leads to situations where an application may be able to receive or respond to context objects that are embedded in a more complex type, but not the more complex type itself. For example, a pricing chart might respond to an `Instrument` but doesn't know how to handle a `Position`. -To facilitate context linking in such situations it is recommended that applications `broadcast` each context type that other apps (listening on a User Channel or App Channel) may wish to process, starting with the simpler types, followed by the complex type. Doing so allows applications to filter the context types they receive by adding listeners for specific context types - but requires that the application broadcasting context make multiple broadcast calls in quick succession when sharing its context. +The [Context specification](../../context/spec#assumptions) recommends that complex context objects are defined using simpler context types for particular fields. For example, a `Position` is composed of an `Instrument` and a holding amount. This leads to situations where an application may be able to receive or respond to context objects that are embedded in a more complex type, but not the more complex type itself. For example, a pricing chart might respond to an `Instrument` but doesn't know how to handle a `Position`. +To facilitate context linking in such situations it is recommended that applications `broadcast` each context type that other apps (listening on a User Channel or App Channel) may wish to process, starting with the simpler types, followed by the complex type. Doing so allows applications to filter the context types they receive by adding listeners for specific context types - but requires that the application broadcasting context make multiple broadcast calls in quick succession when sharing its context. diff --git a/src/api/ImplementationMetadata.ts b/src/api/ImplementationMetadata.ts index ba68d867a..0de0eedda 100644 --- a/src/api/ImplementationMetadata.ts +++ b/src/api/ImplementationMetadata.ts @@ -3,6 +3,8 @@ * Copyright FINOS FDC3 contributors - see NOTICE file */ +import { AppMetadata } from './AppMetadata'; + /** * Metadata relating to the FDC3 Desktop Agent implementation and its provider. */ @@ -17,4 +19,7 @@ export interface ImplementationMetadata { /** The version of the provider of the FDC3 Desktop Agent Implementation (e.g. 5.3.0). */ readonly providerVersion?: string; + + /** The calling application instance's own metadata, according to the Desktop Agent (MUST include at least the `appId` and `instanceId`). */ + readonly appMetadata: AppMetadata; } diff --git a/test/Methods.test.ts b/test/Methods.test.ts index 04c40533f..3584de0d2 100644 --- a/test/Methods.test.ts +++ b/test/Methods.test.ts @@ -376,6 +376,7 @@ describe('test version comparison functions', () => { const metaOneTwo: ImplementationMetadata = { fdc3Version: '1.2', provider: 'test', + appMetadata: { appId: 'dummy', name: 'dummy' }, }; expect(versionIsAtLeast(metaOneTwo, '1.1')).toBe(true); expect(versionIsAtLeast(metaOneTwo, '1.2')).toBe(true); @@ -385,6 +386,7 @@ describe('test version comparison functions', () => { const metaOneTwoOne: ImplementationMetadata = { fdc3Version: '1.2.1', provider: 'test', + appMetadata: { appId: 'dummy', name: 'dummy' }, }; expect(versionIsAtLeast(metaOneTwoOne, '1.1')).toBe(true); expect(versionIsAtLeast(metaOneTwoOne, '1.2')).toBe(true);