Skip to content

Commit

Permalink
Merge pull request #492 from ChartIQ/451-improve-addContextListener-s…
Browse files Browse the repository at this point in the history
…pec-and-docs

451 Improve spec and documentation of fdc3.addContentListener to mention joining channels
  • Loading branch information
rikoe authored Dec 23, 2021
2 parents f5a2f6f + 19a43e1 commit 4035226
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 53 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Clarified existing behavior of `joinChannel` and `addIntentListener` when joining a channel ([#454](https://github.com/finos/FDC3/pull/454))
* Clarified numerous aspects of the existing `raiseIntent` behavior in the spec and documentation ([#461](https://github.com/finos/FDC3/pull/461))
* Updated Methods.ts to support the updated signature for `addContextListener` introduced in FDC3 1.2 ([#462](https://github.com/finos/FDC3/pull/462))
* Clarified the description of the addContextListener functions from the Desktop Agent and Channel APIs in spec and docs. ([#492](https://github.com/finos/FDC3/pull/492))
* Clarified that implementing `fdc3.getInfo()` is required for compliance with the FDC3 standard ([#515](https://github.com/finos/FDC3/pull/515))


## [npm v1.2.0] - 2021-04-19

### Added
Expand Down
3 changes: 3 additions & 0 deletions docs/api/ref/Channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public addContextListener(contextType: string | null, handler: ContextHandler):
```
Adds a listener for incoming contexts of the specified _context type_ whenever a broadcast happens on this channel.

If, when this function is called, the channel already contains context that would be passed to the listener it is NOT called or passed this context automatically (this behavior differs from that of the [`fdc3.addContextListener`](DesktopAgent#addcontextlistener) function). Apps wishing to access to the current context of the channel should instead call the [`getCurrentContext(contextType)`](#getcurrentcontext) function.


```ts
/**
* @deprecated Use `addContextListener(null, handler)` instead of `addContextListener(handler)`
Expand Down
6 changes: 4 additions & 2 deletions docs/api/ref/DesktopAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ addContextListener(handler: ContextHandler): Promise<Listener>;
```
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 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.
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.

#### Examples
```js
Expand Down Expand Up @@ -103,7 +105,7 @@ broadcast(context: Context): Promise<void>;

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.

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.
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.

If you are working with complex context types composed of other simpler types (as recommend by the [Context specification](../../context/spec#assumptions)) then you should broadcast each individual type (starting with the simpler types, followed by the complex type) that you want other apps to be able to respond to. Doing so allows applications to filter the context types they receive by adding listeners for specific context types.

Expand Down
117 changes: 67 additions & 50 deletions docs/api/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,29 +227,40 @@ if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), '1.2')) {

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.

There are two types of channels, which are functionally identical, but have different visibility and discoverability semantics.
There are two types of channels, which are functionally identical, but have different visibility and discoverability semantics:

1. The 'system' channels, which have a well understood identity.
1. **System 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).

> **Deprecation notice:** Earlier versions of FDC3 include the concept of a 'global' system channel
for backwards compatibility with FDC3 1.0. In future, there won't be a 'global' channel
(see [below](#the-global-channel) for more detail).

2. The 'app' channels, which have a transient identity and need to be revealed
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)

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).

### Joining Channels
Apps can join channels. An app can only be joined to one channel at a time.
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.

When an app is joined to a channel, calls to `fdc3.broadcast` will be routed to that channel and listeners added through `fdc3.addContextListener` will receive context broadcasts from other apps also joined to that channel. If an app is not joined to a channel `fdc3.broadcast` will be a no-op and handler functions added with `fdc3.addContextListener` will not receive any broadcasts. However, apps can still choose to listen and broadcast to specific channels via the methods on the `Channel` class.
### Joining System Channels
Apps can join _System channels_. An app can only be joined to one 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 joins a 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.

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. Always, there SHOULD be a clear UX indicator of what channel an app is joined to.

### The 'global' Channel
#### The 'global' Channel

> **Deprecation notice**
>
Expand All @@ -262,70 +273,72 @@ or through a channel selection UI.

The 'system' channels include a 'global' channel which serves as the backwards compatible layer with the 'send/broadcast context' behavior in FDC3 1.0. A desktop agent MAY choose to make membership in the 'global' channel the default state for apps on start up.

The 'global' channel should be returned as part of the response from the `fdc3.getSystemChannels` call. Desktop Agents may want to filter out the 'global' option in their UI for system channel pickers.
The 'global' channel should be returned as part of the response from the [`getSystemChannels()`](ref/DesktopAgent#getsystemchannels) call. Desktop Agents may want to filter out the 'global' option in their UI for system channel pickers.


### Examples
#### Examples

An app queries the current context of the `red` channel.
An app joins a system channel by name and can send and receive context:

```js
const redChannel = await fdc3.getOrCreateChannel('red');
const context = await redChannel.getCurrentContext('fdc3.instrument');
```

An app can still explicitly receive context events on any channel, regardless of the channel it is currently joined to.
//retrieve a list of system channels
const systemChannels = await fdc3.getSystemChannels();

```js
// check for current fdc3 channel
let joinedChannel = await fdc3.getCurrentChannel()
//current channel is null, as the app is not currently joined to a channel
//join a channel from the list returned
await fdc3.joinChannel(systemChannels[0].id);

const redChannel = await fdc3.getSystemChannels.filter(c => c.id === 'red')
const context = await redChannel.getCurrentContext('fdc3.instrument')
// context is instrument AAPL on the global channel
//broadcast your context
fdc3.broadcast({type: 'fdc3.instrument', id: {ticker: 'MSFT'}});

fdc3.joinChannel('blue')
joinedChannel = await fdc3.getCurrentChannel()
//current channel is now the 'blue' channel
//add a Context listener, which will receive the current context immediately
const instrumentListener = fdc3.addContextListener('fdc3.instrument', (context) => {
//do something with context
});

//some time later
instrumentListener.unsubscribe();
```

### Direct Listening and Broadcast on Channels
While joining 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.addBroadcastListener_ and the _Channel.broadcast_ APIs provides an app with fine-grained controls for specific channels. This is especially useful for working with dynamic _App Channels_.

### 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`.
Many Desktop Agents will provide a user interface for joining applications to channels, hence, it is not necessary to explicitly join the channel:

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.
```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'}});
```

### 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_.

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.

### Examples
To find a system 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
// returns an array of channels
const allChannels = await fdc3.getSystemChannels();
const redChannel = allChannels.find(c => c.id === 'red');
```
#### Joining channels
#### Examples

To join a system channel. one calls
An app can send and receive context events on any number of channels, without joining them, by retrieving their `Channel` objects:

```js
fdc3.joinChannel(redChannel.id);
```

Calling _fdc3.broadcast_ will now route context to the joined channel.
const myChannel = await fdc3.getOrCreateChannel('myChannel');

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.
//broadcast your context
myChannel.broadcast({type: 'fdc3.instrument', id: {ticker: 'MSFT'}});

#### 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.
//listen for broadcasts
const instrumentListener = myChannel.addContextListener('fdc3.instrument', (context) => {
//do something with context
});
//some time later
instrumentListener.unsubscribe();
```

To get (or create) a channel reference, then interact with it
An app queries the current context of an App channel (as current context is not automatically received on adding the listener):

```js
const appChannel = await fdc3.getOrCreateChannel('my_custom_channel');
Expand All @@ -335,9 +348,13 @@ const current = await appChannel.getCurrentContext();
await appChannel.addContextListener(null, context => {...});
// broadcast to the channel
await appChannel.broadcast(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`.

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.

Expand Down

0 comments on commit 4035226

Please sign in to comment.