diff --git a/CHANGELOG.md b/CHANGELOG.md index 9560c1e43..70db32517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Upgraded web access statements from SHOULD to MUST in the API specification ([#440](https://github.com/finos/FDC3/pull/440)) * Updated copyright notices ([#467](https://github.com/finos/FDC3/pull/467)) * Adjusted wording in API spec and documentation to acknowledge the possibility of methods of intent resolution other than a resolver UI ([#461](https://github.com/finos/FDC3/pull/461)) +* Replaced 'System channels' with 'User channels' throughout the spec, documentation, API and methods.ts. Clarified spec and documentation where it is referring to User channels vs. App channels. Added support to methods.ts for automatic fallback to `getSystemChannels` if `getUserChannels` doesn't exist. ([#470](https://github.com/finos/FDC3/pull/479)) * Moved the Icon type definition into the Types documentation page for consistency with other types. ([#493](https://github.com/finos/FDC3/pull/493) * The `fdc3.joinChannel()`, `fdc3.getCurrentChannel()` and `fdc3.leaveCurrentChannel()` functions have been made optional for FDC3 API compliance, but are recommended through the application of the SHOULD keyword. ([#512](https://github.com/finos/FDC3/pull/512)) * All DesktopAgent and Channel API functions are now async for consistency, changing the return type of the `broadcast`, `addIntentListener`, `addContextListener` and `getInfo` functions ([#516](https://github.com/finos/FDC3/pull/516)) diff --git a/docs/api/ref/Channel.md b/docs/api/ref/Channel.md index 18938b5a8..718ff6d57 100644 --- a/docs/api/ref/Channel.md +++ b/docs/api/ref/Channel.md @@ -8,7 +8,7 @@ hide_title: true Represents a context channel that applications can join to share context data. -A channel can be either a well-known "system" channel (retrieved with [`getSystemChannels`](DesktopAgent#getsystemchannels)) or a custom "app" channel (obtained through [`getOrCreateChannel`](DesktopAgent#getorcreatechannel)). +A channel can be either a "User" channel (retrieved with [`getUserChannels`](DesktopAgent#getuserchannels)) or a custom "App" channel (obtained through [`getOrCreateChannel`](DesktopAgent#getorcreatechannel)). Channels each have a unique identifier, some display metadata and operations for broadcasting context to other applications, or receiving context from other applications. @@ -34,9 +34,9 @@ interface Channel { * [`Context`](Types#context) * [`Listener`](Types#listener) -* [`DesktopAgent.getSystemChannels`](DesktopAgent#getsystemchannels) +* [`DesktopAgent.getUserChannels`](DesktopAgent#getuserchannels) * [`DesktopAgent.getOrCreateChannel`](DesktopAgent#getorcreatechannel) -* [`DesktopAgent.joinChannel`](DesktopAgent#joinchannel) +* [`DesktopAgent.joinUserChannel`](DesktopAgent#joinuserchannel) ## Properties @@ -46,7 +46,7 @@ interface Channel { public readonly id: string; ``` -Uniquely identifies the channel. It is either assigned by the desktop agent (system channel) or defined by an application (app channel). +Uniquely identifies the channel. It is either assigned by the desktop agent (User Channel) or defined by an application (App Channel). ### `type` @@ -62,7 +62,7 @@ Can be _system_ or _app_. public readonly displayMetadata?: DisplayMetadata; ``` -DisplayMetadata can be used to provide display hints for channels intended to be visualized and selectable by end users. +DisplayMetadata can be used to provide display hints for User Channels intended to be visualized and selectable by end users. #### See also * [`DisplayMetadata`](Metadata#displaymetadata) @@ -133,7 +133,7 @@ instrumentListener.unsubscribe(); public broadcast(context: Context): Promise; ``` -Broadcasts a context on the channel. This function can be used without first joining the channel, allowing applications to broadcast on channels that they aren't a member of. +Broadcasts a context on the channel. This function can be used without first joining the channel, allowing applications to broadcast on both App Channels and User Channels that they aren't a member of. If the broadcast is denied by the channel or the channel is not available, the method will return an `Error` with a string from the [`ChannelError`](ChannelError) enumeration. diff --git a/docs/api/ref/DesktopAgent.md b/docs/api/ref/DesktopAgent.md index d576a1bc6..ded157f28 100644 --- a/docs/api/ref/DesktopAgent.md +++ b/docs/api/ref/DesktopAgent.md @@ -22,7 +22,7 @@ interface DesktopAgent { broadcast(context: Context): Promise; addContextListener(contextType: string | null, handler: ContextHandler): Promise; /** - * @deprecated 'Use `addContextListener(null, handler)` instead of `addContextListener(handler)` + * @deprecated Use `addContextListener(null, handler)` instead of `addContextListener(handler)` */ addContextListener(handler: ContextHandler): Promise; @@ -35,14 +35,23 @@ interface DesktopAgent { // channels getOrCreateChannel(channelId: string): Promise; - getSystemChannels(): Promise>; + getUserChannels(): Promise>; // optional channel management functions - joinChannel(channelId: string) : Promise; + joinUserChannel(channelId: string) : Promise; getCurrentChannel() : Promise; leaveCurrentChannel() : Promise; //implementation info getInfo(): Promise; + + /** + * @deprecated Use `getUserChannels()` instead of `getSystemChannels()` + */ + getSystemChannels(): Promise>; + /** + * @deprecated Use `joinUserChannel()` instead of `joinChannel()` + */ + joinChannel(channelId: string) : Promise; } ``` @@ -59,9 +68,7 @@ addContextListener(handler: ContextHandler): Promise; ``` Adds a listener for incoming context broadcasts from the Desktop Agent. If the consumer is only interested in a context of a particular type, they can specify that type. If the consumer is able to receive context of any type or will inspect types received, then they can pass `null` as the `contextType` parameter to receive all context types. -Context broadcasts are only received from apps that are joined to the same channel as the listening application, hence, if the application is not currently joined to a channel no broadcasts will be received. - -If this function is called after the app has joined a channel and the channel already contains context that would be passed to the context listener, then it will be called immediately with that context. If a listener is added first and then the app is joined to a channel, with current context that would be passed to the context listener, then the listener will again be called immediately with that context. +Context broadcasts are only received from apps that are joined to the same User Channel as the listening application, hence, if the application is not currently joined to a User Channel no broadcasts will be received. If this function is called after the app has already joined a channel and the channel already contains context that would be passed to the context listener, then it will be called immediately with that context. #### Examples ```js @@ -105,7 +112,7 @@ const listener = await fdc3.addIntentListener('StartChat', context => { broadcast(context: Context): Promise; ``` -Publishes context to other apps on the desktop. Calling `broadcast` at the `DesktopAgent` scope will push the context to whatever `Channel` the app is joined to. If the app is not currently joined to a channel, calling `fdc3.broadcast` will have no effect. Apps can still directly broadcast and listen to context on any channel via the methods on the `Channel` class. +Publishes context to other apps on the desktop. Calling `broadcast` at the `DesktopAgent` scope will push the context to whatever _User Channel_ the app is joined to. If the app is not currently joined to a channel, calling `fdc3.broadcast` will have no effect. Apps can still directly broadcast and listen to context on any channel via the methods on the `Channel` class. DesktopAgent implementations should ensure that context messages broadcast to a channel by an application joined to it are not delivered back to that same application. @@ -251,11 +258,10 @@ If the resolution fails, the promise will return an `Error` with a string from t getCurrentChannel() : Promise; ``` -Optional function that returns the `Channel` object for the current 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. - #### Examples ```js @@ -299,7 +305,7 @@ if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), "1.2")) { getOrCreateChannel(channelId: string): Promise; ``` -Returns a Channel object for the specified channel, creating it (as an _App_ channel) - if it does not exist. +Returns a Channel object for the specified channel, creating it (as an _App Channel_) - if it does not exist. `Error` with a string from the [`ChannelError`](ChannelError) enumeration if channel could not be created or access was denied. #### Example @@ -307,7 +313,7 @@ Returns a Channel object for the specified channel, creating it (as an _App_ cha ```js try { const myChannel = await fdc3.getOrCreateChannel("myChannel"); - const myChannel.addContextListener(null, context => {}); + myChannel.addContextListener(null, context => { /* do something with context */}); } catch (err){ //app could not register the channel @@ -318,30 +324,42 @@ catch (err){ #### See also * [`Channel`](Channel) -### `getSystemChannels` +### `getUserChannels` ```ts -getSystemChannels() : Promise>; +getUserChannels() : Promise>; ``` -Retrieves a list of the System channels available for the app to join. + +Retrieves a list of the User Channels available for the app to join. #### Example ```js -const systemChannels = await fdc3.getSystemChannels(); -const redChannel = systemChannels.find(c => c.id === 'red'); +const userChannels = await fdc3.getUserChannels(); +const redChannel = userChannels.find(c => c.id === 'red'); ``` #### See also * [`Channel`](Channel) +### `getSystemChannels` +```ts +/** + * @deprecated Use `getUserChannels` instead. + */ +getSystemChannels() : Promise>; +``` + +Alias to the [`getUserChannels`](#getuserchannels) function provided for backwards compatibility with version 1.1 & 1.2 of the FDC3 standard. +#### See also +* [`getUserChannels`](#getuserchannels) -### `joinChannel` +### `joinUserChannel` ```ts -joinChannel(channelId: string) : Promise; +joinUserChannel(channelId: string) : Promise; ``` -Optional function that joins the app to the specified 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. +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. @@ -354,19 +372,32 @@ Rejects with an error if the channel is unavailable or the join request is denie #### Examples ```js -// get all system channels -const channels = await fdc3.getSystemChannels(); +// get all user channels +const channels = await fdc3.getUserChannels(); -// create UI to pick from the system channels +// create UI to pick from the User channels // join the channel on selection -fdc3.joinChannel(selectedChannel.id); +fdc3.joinUserChannel(selectedChannel.id); ``` #### See also -* [`getSystemChannels`](#getSystemChannels) +* [`getUserChannels`](#getuserchannels) +### `joinChannel` + +```ts +/** + * @deprecated Use `joinUserChannel()` instead of `joinChannel()` + */ +joinChannel(channelId: string) : Promise; +``` +Alias to the [`joinUserChannel`](#joinUserChannel) function provided for backwards compatibility with version 1.1 & 1.2 of the FDC3 standard. + +#### See also +* [`joinUserChannel`](#joinuserchannel) + ### `leaveCurrentChannel` @@ -374,9 +405,9 @@ fdc3.joinChannel(selectedChannel.id); leaveCurrentChannel() : Promise; ``` -Optional function that removes the app from any 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 removes the app from any 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. -Context broadcast and listening through the top-level `fdc3.broadcast` and `fdc3.addContextListener` will be a no-op when the app is not joined to a channel. +Context broadcast and listening through the top-level `fdc3.broadcast` and `fdc3.addContextListener` will be a no-op when the app is not joined to a User channel. #### Examples diff --git a/docs/api/ref/Errors.md b/docs/api/ref/Errors.md index 2aa7f8749..53dd365c3 100644 --- a/docs/api/ref/Errors.md +++ b/docs/api/ref/Errors.md @@ -51,10 +51,10 @@ enum ChannelError { } ``` -Contains constants representing the errors that can be encountered when calling channels using the [`joinChannel`](DesktopAgent#joinchannel) or [`getOrCreateChannel`](DesktopAgent#getorcreatechannel) methods, or the [`getCurrentContext`](Channel#getcurrentcontext), [`broadcast`](Channel#broadcast) or [`addContextListener`](Channel#addcontextlistener) methods on the `Channel` object. +Contains constants representing the errors that can be encountered when calling channels using the [`joinUserChannel`](DesktopAgent#joinuserchannel) or [`getOrCreateChannel`](DesktopAgent#getorcreatechannel) methods, or the [`getCurrentContext`](Channel#getcurrentcontext), [`broadcast`](Channel#broadcast) or [`addContextListener`](Channel#addcontextlistener) methods on the `Channel` object. #### See also -* [`DesktopAgent.joinChannel`](DesktopAgent#joincannel) +* [`DesktopAgent.joinUserChannel`](DesktopAgent#joinuserchannel) * [`DesktopAgent.getOrCreateChannel`](DesktopAgent#getorcreatechannel) * [`Channel.broadcast`](Channel#broadcast) * [`Channel.addContextListener`](Channel#addcontextlistener) diff --git a/docs/api/ref/Metadata.md b/docs/api/ref/Metadata.md index a5bee32f1..01bf9ff0f 100644 --- a/docs/api/ref/Metadata.md +++ b/docs/api/ref/Metadata.md @@ -15,7 +15,7 @@ interface AppIntent { An interface that represents the binding of an intent to apps, returned as part of intent disocvery. For each intent, it reference the applications that support that intent. -#### See also +### See also * [`AppMetadata`](AppMetadata) * [`IntentMetadata`](IntentMetadata) * [`DesktopAgent.findIntent`](DesktopAgent#findintent) @@ -65,14 +65,13 @@ Optionally, extra information from the app directory can be returned, to aid in 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` ```ts @@ -85,7 +84,7 @@ In situations where a desktop agent connects to multiple app directories or mult A desktop agent (typically for _system_ channels) may want to provide additional information about how a channel can be represented in a UI. A common use case is for color linking. -#### Properties +### Properties #### `name` @@ -111,10 +110,10 @@ glyph: string; A URL of an image that can be used to display this channel. -#### See also +### See also * [`Channel`](Channel) -* [`DesktopAgent.getSystemChannels`](DesktopAgent#getsystemchannels) +* [`DesktopAgent.getUserChannels`](DesktopAgent#getuserchannels) ## `ImplementationMetadata` @@ -128,7 +127,7 @@ public interface ImplementationMetadata { Metadata relating to the FDC3 [DesktopAgent](DesktopAgent) object and its provider, including the supported version of the FDC3 specification and the name of the provider of the implementation. -#### See also +### See also * [`DesktopAgent.getInfo`](DesktopAgent#getInfo) ## `IntentMetadata` @@ -143,7 +142,7 @@ interface IntentMetadata { The interface used to describe an intent within the platform. -#### See also +### See also * [`AppIntent.intent`](AppIntent) ## `IntentResolution` @@ -191,7 +190,7 @@ try { catch (err) { ... } ``` -#### See also +### See also * [`DesktopAgent.raiseIntent`](DesktopAgent#raiseintent) * [`DesktopAgent.raiseIntentForContext`](DesktopAgent#raiseintentforcontext) * [`TargetApp`](TargetApp) diff --git a/docs/api/spec.md b/docs/api/spec.md index 0294969c3..b85a476e3 100644 --- a/docs/api/spec.md +++ b/docs/api/spec.md @@ -237,11 +237,14 @@ Context channels allows a set of apps to share a stateful piece of data between There are two types of channels, which are functionally identical, but have different visibility and discoverability semantics: -1. **_System channels_**, which: + +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 [`getSystemChannels()`](ref/DesktopAgent#getsystemchannels) API call), - * can be 'joined' (via the [`joinChannel()`](ref/DesktopAgent#joinchannel) API call). + * are discoverable (via the [`getUserChannels()`](ref/DesktopAgent#getuserchannels) API call), + * can be 'joined' (via the [`joinUserChannel()`](ref/DesktopAgent#joinuserchannel) API call). + + > 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). > **Note:** Earlier versions of FDC3 included the concept of a 'global' system channel which was deprecated in FDC3 1.2 and removed in FDC 2.0. @@ -252,87 +255,59 @@ There are two types of channels, which are functionally identical, but have diff * are not discoverable, * are interacted with via the [Channel API](ref/Channel) (accessed via the desktop agent [`getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel) API call) - -Channels are interacted with via `broadcast` and `addContextListener` functions, allowing an application to send and receive Context objects via the channel. For System channels, these functions are provided on the Desktop Agent, e.g. [`fdc3.broadcast(context)`](ref/DesktopAgent#broadcast), and apply to channels joined via [`fdc3.joinChannel`](ref/DesktopAgent#joinchannel). For App channels, a channel object must be retrieved, via [`fdc3.getOrCreateChannel(channelName)`](ref/DesktopAgent#getorcreatechannel), which provides the functions, e.g. [`myChannel.broadcast(context)`](ref/Channel#broadcast). +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, e.g. [`myChannel.broadcast(context)`](ref/Channel#broadcast). 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 System Channels -Apps can join _System channels_. An app can only be joined to one channel at a time. +### Joining Channels +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 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 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 via the methods on the [`Channel`](ref/Channel) class. +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. -When an app joins a channel, or adds a context listener when already joined to a channel, it will automatically receive the current context for that channel. +When an app joins a User channel, or adds a context listener when already joined to a channel, it will automatically receive the current context for that channel. -It is possible that a call to join a channel could be rejected. If for example, the desktop agent wanted to implement controls around what data apps can access. +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). There SHOULD always be a clear UX indicator of what channel an app is joined to. -#### Examples - -An app joins a system channel by name and can send and receive context: - -```js -//retrieve a list of system channels -const systemChannels = await fdc3.getSystemChannels(); - -//join a channel from the list returned -await fdc3.joinChannel(systemChannels[0].id); +### Direct Listening and Broadcast on Channels +While joining User channels automates a lot of the channel behavior for an app, it has the limitation that an app can belong to only one channel at a time. Listening and Broadcasting to channels using the [`Channel.addContextListener`](ref/Channel#addcontextlistener) and the [`Channel.broadcast`](ref/Channel#broadcast) APIs provides an app with fine-grained controls for specific channels. This is especially useful for working with dynamic _App Channels_. -//add a Context listener, which will receive the current context immediately -const instrumentListener = fdc3.addContextListener('fdc3.instrument', (context) => { - //do something with context -}); +### 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`. -//broadcast your context -fdc3.broadcast({type: 'fdc3.instrument', id: {ticker: 'MSFT'}}); +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. -//some time later -instrumentListener.unsubscribe(); -``` - -Many Desktop Agents will provide a user interface for joining applications to channels, hence, it is not necessary to explicitly join the channel: +### Examples +To find a User channel, one calls: ```js -//add a Context listener without joining a channel first, -//The listener will receive the current context when the user joins the app to a channel -const instrumentListener = fdc3.addContextListener('fdc3.instrument', (context) => { - //do something with context -}); - -//broadcast your context to the channel the user has joined you to -//If not currently joined to a channel this will be ignored -fdc3.broadcast({type: 'fdc3.instrument', id: {ticker: 'MSFT'}}); +// returns an array of channels +const allChannels = await fdc3.getUserChannels(); +const redChannel = allChannels.find(c => c.id === 'red'); ``` -### Direct Listening and Broadcast on App Channels -While joining system channels automates a lot of the channel behavior for an app, it has the limitation in that an app can belong to only one channel at a time. Listening and Broadcasting to channels using the [`Channel.addContextListener`](ref/Channel#addcontextlistener) and the [`Channel.broadcast`](ref/Channel#broadcast) APIs provides an app with fine-grained controls for specific channels and is used for working with dynamic _App Channels_. +#### Joining User 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 join a User channel, one calls: -The current context of a System Channel is automatically received when the channel is joined or a context listener added (when already joined to a channel, however, when working with the Channel API, the current context of the Channel is retrieved manually via [`Channel.getCurrentContext`](ref/Channel#getcurrentcontext) (returning either the last context broadcast or the last context of a specified type). +```js +fdc3.joinUserChannel(redChannel.id); +``` -#### Examples +Calling `fdc3.broadcast` will now route context to the joined channel. -An app can send and receive context events on any number of channels, without joining them, by retrieving their `Channel` objects: +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 joined to the channel. -```js -const myChannel = await fdc3.getOrCreateChannel('myChannel'); + > 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. -//broadcast your context -myChannel.broadcast({type: 'fdc3.instrument', id: {ticker: 'MSFT'}}); +#### App Channels -//listen for broadcasts -const instrumentListener = myChannel.addContextListener('fdc3.instrument', (context) => { - //do something with context -}); -//some time later -instrumentListener.unsubscribe(); -``` +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. -An app queries the current context of an App channel (as current context is not automatically received on adding the listener): +To get (or create) a channel reference, then interact with it: ```js const appChannel = await fdc3.getOrCreateChannel('my_custom_channel'); @@ -365,11 +340,6 @@ joinedChannel = await fdc3.getCurrentChannel() if another application broadcasts to "my_custom_channel" (by retrieving it and broadcasting to it via `myChannel.broadcast()`) then the broadcast will be received by the specific listener (`myChannelListener`) but NOT by the listener for joined channels (`listener`). -### 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 System 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. - ## APIs The APIs are defined in TypeScript in [src], with documentation generated in the [docs] folder. diff --git a/docs/context/ref/Instrument.md b/docs/context/ref/Instrument.md index 25d5927c9..07d2c7077 100644 --- a/docs/context/ref/Instrument.md +++ b/docs/context/ref/Instrument.md @@ -59,7 +59,7 @@ const instrument = { } } -fdc3.joinChannel('red') +fdc3.joinUserChannel('Channel 1') fdc3.broadcast(instrument) ``` diff --git a/docs/context/ref/InstrumentList.md b/docs/context/ref/InstrumentList.md index 718f85e3e..9ac458350 100644 --- a/docs/context/ref/InstrumentList.md +++ b/docs/context/ref/InstrumentList.md @@ -54,7 +54,7 @@ const instruments = { ] } -fdc3.joinChannel('red') +fdc3.joinUserChannel('Channel 1') fdc3.broadcast(instruments) ``` diff --git a/docs/context/ref/Nothing.md b/docs/context/ref/Nothing.md index cf7a3d519..eb7420ddb 100644 --- a/docs/context/ref/Nothing.md +++ b/docs/context/ref/Nothing.md @@ -34,6 +34,6 @@ const nullContext = { type: 'fdc3.nothing' } -fdc3.joinChannel('groupA') +fdc3.joinUserChannel('groupA') fdc3.broadcast(nullContext) ``` \ No newline at end of file diff --git a/docs/supported-platforms.md b/docs/supported-platforms.md index 85ff8b3d6..e913aac5a 100644 --- a/docs/supported-platforms.md +++ b/docs/supported-platforms.md @@ -53,7 +53,7 @@ const instrument = { const result = await fdc3.raiseIntent('ViewAnalysis', instrument) // join the red channel and broadcast data to subscribers -await fdc3.joinChannel('red') +await fdc3.joinUserChannel('red') fdc3.broadcast(instrument) // set up a listener for incoming data diff --git a/src/api/Channel.ts b/src/api/Channel.ts index f4a09a842..e997bcffa 100644 --- a/src/api/Channel.ts +++ b/src/api/Channel.ts @@ -24,7 +24,7 @@ export interface Channel { /** * Channels may be visualized and selectable by users. DisplayMetadata may be used to provide hints on how to see them. - * For app channels, displayMetadata would typically not be present + * For App channels, displayMetadata would typically not be present. */ readonly displayMetadata?: DisplayMetadata; @@ -33,7 +33,7 @@ export interface Channel { * top-level FDC3 `broadcast` function. * * Note that this function can be used without first joining the channel, allowing applications to broadcast on - * channels that they aren't a member of. + * User channels that they aren't a member of. * * Channel implementations should ensure that context messages broadcast by an application on a channel should * not be delivered back to that same application if they are joined to the channel. diff --git a/src/api/DesktopAgent.ts b/src/api/DesktopAgent.ts index f1d9846a6..e9099531b 100644 --- a/src/api/DesktopAgent.ts +++ b/src/api/DesktopAgent.ts @@ -150,8 +150,8 @@ export interface DesktopAgent { findInstances(app: TargetApp): Promise>; /** - * Publishes context to other apps on the desktop. Calling `broadcast` at the `DesktopAgent` scope will push the context to whatever `Channel` the app is joined to. If the app is not currently joined to a channel, calling `fdc3.broadcast` will have no effect. Apps can still directly broadcast and listen to context on any channel via the methods on the `Channel` class. - * + * Publishes context to other apps on the desktop. Calling `broadcast` at the `DesktopAgent` scope will push the context to whatever _User Channel_ the app is joined to. If the app is not currently joined to a channel, calling `fdc3.broadcast` will have no effect. Apps can still directly broadcast and listen to context on any channel via the methods on the `Channel` class. + * * DesktopAgent implementations should ensure that context messages broadcast to a channel by an application joined to it should not be delivered back to that same application. * * ```javascript @@ -161,7 +161,6 @@ export interface DesktopAgent { * ticker: 'AAPL' * } * }; - * fdc3.broadcast(context); * ``` */ @@ -221,14 +220,14 @@ export interface DesktopAgent { addIntentListener(intent: string, handler: ContextHandler): Promise; /** - * Adds a listener for incoming context broadcast from the Desktop Agent. + * Adds a listener for incoming context broadcasts from the Desktop Agent. * @deprecated use `addContextListener(null, handler)` instead of `addContextListener(handler)`. */ addContextListener(handler: ContextHandler): Promise; /** - * Adds a listener for incoming context broadcasts from the Desktop Agent. If the consumer is only interested in a context of a particular type, they can they can specify that type. If the consumer is able to receive context of any type or will inspect types received, then they can pass `null` as the `contextType` parameter to receive all context types. - * Context broadcasts are only received from apps that are joined to the same channel as the listening application, hence, if the application is not currently joined to a channel no broadcasts will be received. If this function is called after the app has already joined a channel and the channel already contains context that would be passed to the context listener, then it will be called immediately with that context. + * Adds a listener for incoming context broadcasts from the Desktop Agent via User channels. If the consumer is only interested in a context of a particular type, they can they can specify that type. If the consumer is able to receive context of any type or will inspect types received, then they can pass `null` as the `contextType` parameter to receive all context types. + * Context broadcasts are only received from apps that are joined to the same User channel as the listening application, hence, if the application is not currently joined to a channel no broadcasts will be received. If this function is called after the app has already joined a channel and the channel already contains context that would be passed to the context listener, then it will be called immediately with that context. * ```javascript * // any context * const listener = fdc3.addContextListener(null, context => { ... }); @@ -239,12 +238,17 @@ export interface DesktopAgent { addContextListener(contextType: string | null, handler: ContextHandler): Promise; /** - * Retrieves a list of the System channels available for the app to join + * Retrieves a list of the User channels available for the app to join. + */ + getUserChannels(): Promise>; + + /** + * @deprecated Alias to the `getUserChannels function provided for backwards compatibility with version 1.1 and 1.2 of the FDC3 standard. */ getSystemChannels(): Promise>; /** - * Optional function that joins the app to the specified 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. + * 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 the channel already contains context that would be passed to context listeners assed via `fdc3.addContextListener` then those listeners will be called immediately with that context. @@ -252,16 +256,21 @@ export interface DesktopAgent { * Rejects with an error if the channel is unavailable or the join request is denied. The error string will be drawn from the `ChannelError` enumeration. * ```javascript * // get all system channels - * const channels = await fdc3.getSystemChannels(); - * // create UI to pick from the system channels + * const channels = await fdc3.getUserChannels(); + * // create UI to pick from the User channels * // join the channel on selection - * fdc3.joinChannel(selectedChannel.id); + * fdc3.joinUserChannel(selectedChannel.id); * ``` */ + joinUserChannel(channelId: string): Promise; + + /** + * @deprecated Alias to the `joinUserChannel` function provided for backwards compatibility with version 1.1 and 1.2 of the FDC3 standard. + */ joinChannel(channelId: string): Promise; /** - * Returns a channel with the given identity. Either stands up a new channel or returns an existing channel. + * Returns an App channel with the given identity. Either stands up a new channel or returns an existing channel. * It is up to applications to manage how to share knowledge of these custom channels across windows and to manage * channel ownership and lifecycle. * `Error` with a string from the `ChannelError` enumeration. @@ -269,14 +278,14 @@ export interface DesktopAgent { getOrCreateChannel(channelId: string): Promise; /** - * Optional function that returns the `Channel` object for the current 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. */ getCurrentChannel(): Promise; /** - * Optional function that removes the app from any 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 removes the app from any 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. * * Context broadcast and listening through the top-level `fdc3.broadcast` and `fdc3.addContextListener` will be a no-op when the app is not on a channel. * ```javascript diff --git a/src/api/Methods.ts b/src/api/Methods.ts index 56289da38..7f83e1b84 100644 --- a/src/api/Methods.ts +++ b/src/api/Methods.ts @@ -87,12 +87,36 @@ export function addContextListener( } } +export function getUserChannels(): Promise { + return rejectIfNoGlobal(() => { + //fallback to getSystemChannels for FDC3 <2.0 implementations + if (window.fdc3.getUserChannels) { + return window.fdc3.getUserChannels(); + } else { + return window.fdc3.getSystemChannels(); + } + }); +} + export function getSystemChannels(): Promise { - return rejectIfNoGlobal(() => window.fdc3.getSystemChannels()); + //fallforward to getUserChannels for FDC3 2.0+ implementations + return getUserChannels(); +} + +export function joinUserChannel(channelId: string): Promise { + return rejectIfNoGlobal(() => { + //fallback to joinChannel for FDC3 <2.0 implementations + if (window.fdc3.joinUserChannel) { + return window.fdc3.joinUserChannel(channelId); + } else { + return window.fdc3.joinChannel(channelId); + } + }); } export function joinChannel(channelId: string): Promise { - return rejectIfNoGlobal(() => window.fdc3.joinChannel(channelId)); + //fallforward to joinUserChannel for FDC3 2.0+ implementations + return joinUserChannel(channelId); } export function getOrCreateChannel(channelId: string): Promise { diff --git a/test/Methods.test.ts b/test/Methods.test.ts index 57ad928a1..0df97c90d 100644 --- a/test/Methods.test.ts +++ b/test/Methods.test.ts @@ -13,9 +13,11 @@ import { getCurrentChannel, getInfo, getOrCreateChannel, + getUserChannels, getSystemChannels, ImplementationMetadata, joinChannel, + joinUserChannel, leaveCurrentChannel, open, raiseIntent, @@ -43,6 +45,10 @@ expect.extend({ }, }); +beforeEach(() => { + jest.resetAllMocks(); +}); + describe('test ES6 module', () => { describe('without `window.fdc3` global', () => { test('open should reject', async () => { @@ -79,14 +85,18 @@ describe('test ES6 module', () => { await expect(addContextListener(expect.any(String), expect.any(Object))).rejects.toEqual(UnavailableError); }); - test('getSystemChannels should reject', async () => { - await expect(getSystemChannels()).rejects.toEqual(UnavailableError); + test('getUserChannels should reject', async () => { + await expect(getUserChannels()).rejects.toEqual(UnavailableError); }); test('joinChannel should reject', async () => { await expect(joinChannel(expect.any(String))).rejects.toEqual(UnavailableError); }); + test('joinUserChannel should reject', async () => { + await expect(joinUserChannel(expect.any(String))).rejects.toEqual(UnavailableError); + }); + test('getOrCreateChannel should reject', async () => { await expect(getOrCreateChannel(expect.any(String))).rejects.toEqual(UnavailableError); }); @@ -187,20 +197,36 @@ describe('test ES6 module', () => { expect(window.fdc3.addContextListener).toHaveBeenNthCalledWith(2, null, handler2); }); - test('getSystemChannels should delegate to window.fdc3.getSystemChannels', async () => { + test('getUserChannels should delegate to window.fdc3.getUserChannels', async () => { + await getUserChannels(); + + expect(window.fdc3.getUserChannels).toHaveBeenCalledTimes(1); + expect(window.fdc3.getUserChannels).toHaveBeenCalledWith(); + }); + + test('getSystemChannels should delegate to window.fdc3.getUserChannels', async () => { await getSystemChannels(); - expect(window.fdc3.getSystemChannels).toHaveBeenCalledTimes(1); - expect(window.fdc3.getSystemChannels).toHaveBeenCalledWith(); + expect(window.fdc3.getUserChannels).toHaveBeenCalledTimes(1); + expect(window.fdc3.getUserChannels).toHaveBeenCalledWith(); }); - test('joinChannel should delegate to window.fdc3.joinChannel', async () => { + test('joinChannel should delegate to window.fdc3.joinUserChannel', async () => { const channelId = 'channel'; await joinChannel(channelId); - expect(window.fdc3.joinChannel).toHaveBeenCalledTimes(1); - expect(window.fdc3.joinChannel).toHaveBeenCalledWith(channelId); + expect(window.fdc3.joinUserChannel).toHaveBeenCalledTimes(1); + expect(window.fdc3.joinUserChannel).toHaveBeenCalledWith(channelId); + }); + + test('joinUserChannel should delegate to window.fdc3.joinUserChannel', async () => { + const channelId = 'channel'; + + await joinUserChannel(channelId); + + expect(window.fdc3.joinUserChannel).toHaveBeenCalledTimes(1); + expect(window.fdc3.joinUserChannel).toHaveBeenCalledWith(channelId); }); test('getOrCreateChannel should delegate to window.fdc3.getOrCreateChannel', async () => {