-
Notifications
You must be signed in to change notification settings - Fork 137
/
Copy pathspec.md
579 lines (417 loc) · 35.6 KB
/
spec.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
---
id: spec
sidebar_label: Overview
title: API Overview (next)
---
The role of FDC3 API is to establish a baseline interface for interoperability between applications. Because FDC3 is largely an agreement between existing platforms and applications, standards should be optimized for ease of adoption rather than functional completeness. Functionality absent from a FDC3 specification is in no way a commentary on its importance.
The following sections examine the API's use-cases and core concepts. The APIs a fully defined in both subsequent pages of this Part and a full set of TypeScript definitions in the [src](https://github.com/finos/FDC3/tree/master/src/api) directory of the [FDC3 GitHub repository](https://github.com/finos/FDC3/).
## Components
### Desktop Agent
A Desktop Agent is a desktop component (or aggregate of components) that serves as a launcher and message router (broker) for applications in its domain. A Desktop Agent can be connected to one or more App Directories and will use directories for application identity and discovery. Typically, a Desktop Agent will contain the proprietary logic of a given platform, handling functionality like explicit application interop workflows where security, consistency, and implementation requirements are proprietary.
Examples of Desktop Agents include:
- Autobahn
- Cosaic's Finsemble
- Glue42
- OpenFin
- Refinitiv Eikon
An FDC3-compliant Desktop Agent exposes an FDC3 standard API to applications they have launched. When an App is launched by a Desktop Agent and is given access to the Agent's API to interoperate, it is running in that Desktop Agent's *context*.
### Application
An application is any endpoint on the desktop that is:
- Registered with/known by a Desktop Agent
- Launchable by a Desktop Agent
- Addressable by a Desktop Agent
Examples of endpoints include:
- Native Applications
- Web Applications
- Headless “services” running on the desktop
## Desktop Agent Implementation
The FDC3 API specification consists of interfaces. It is expected that each Desktop Agent will implement these interfaces. A typical implemention would provide implementations for the following interfaces:
- [`DesktopAgent`](ref/DesktopAgent)
- [`Channel`](ref/Channel)
- [`PrivateChannel`](ref/PrivateChannel)
- [`Listener`](ref/Types#listener)
Other interfaces defined in the spec are not critical to define as concrete types. Rather, the Desktop Agent should expect to have objects of the interface shape passed into or out of their library.
### API Access
The FDC3 API can be made available to an application through a number of different methods. In the case of web applications, a Desktop Agent MUST provide the FDC3 API via a global accessible as `window.fdc3`. Implementors MAY additionally make the API available through modules, imports, or other means.
The global `window.fdc3` must only be available after the API is ready to use. To enable applications to avoid using the API before it is ready, implementors MUST provide a global `fdc3Ready` event that is fired when the API is ready for use. Implementations should first check for the existence of the FDC3 API and add a listener for this event if it is not found:
```js
function fdc3Stuff() {
// Make fdc3 API calls here
}
if (window.fdc3) {
fdc3Stuff();
} else {
window.addEventListener('fdc3Ready', fdc3Stuff);
}
```
### Standards vs. Implementation

The surface area of FDC3 standardization (shown in *white* above) itself is quite small in comparison to the extent of a typical desktop agent implementation (in *grey*).
For example:
- workspace management
- user identity and SSO
- entitlements
- UX of application resolution
Are all areas of functionality that any feature-complete desktop agent would implement, but are not currently areas considered for standardization under FDC3.
### Inter-Agent Communication
A goal of FDC3 standards is that applications running in different Desktop Agent contexts on the same desktop would be able to interoperate. And that one Desktop Agent context would be able to discover and launch an application in another Desktop Application context.

Desktop Agent interop is supported by common standards for APIs for App discovery and launching. So, an App in one Desktop Agent context would not need to know a different syntax to call an App in another Desktop Agent context.
An actual connection protocol between Desktop Agents is not currently available in the FDC3 standard, but work on creating one for a future version of the standard is underway (see [GitHub discussion #544](https://github.com/finos/FDC3/discussions/544) for details).
### Desktop Agent API Standard Compliance
An FDC3 Standard compliant Desktop Agent implementation **MUST**:
- Provide the FDC3 API to web applications via a global accessible as [`window.fdc3`](#api-access).
- Provide a global [`fdc3Ready`](#api-access) event that is fired when the API is ready for use.
- Provide a method of resolving ambiguous intents (i.e. those that might be resolved by multiple applications) or unspecified intents (calls to `raiseIntentForContext` that return multiple options), such as a resolver UI.
- Intent resolution MUST take into account any specified input or return context types
- Requests for resolution to apps returning a channel MUST include any apps that are registered as returning a channel with a specific type.
- Return Errors from the [`ChannelError`](ref/Errors#channelerror), [`OpenError`](ref/Errors#openerror), [`ResolveError`](ref/Errors#resolveerror) and [`ResultError`](ref/Errors#resulterror) enumerations as appropriate.
- Accept as input and return as output data structures that are compatibile with the interfaces defined in this Standard.
- Include implementations of the following [Desktop Agent](ref/DesktopAgent) API functions, as defined in this Standard:
- [`addContextListener`](ref/DesktopAgent#addcontextlistener)
- [`addIntentListener`](ref/DesktopAgent#addintentlistener)
- [`broadcast`](ref/DesktopAgent#broadcast)
- [`createPrivateChannel`](ref/DesktopAgent#createprivatechannel)
- [`findInstances`](ref/DesktopAgent#findinstances)
- [`findIntent`](ref/DesktopAgent#findintent)
- [`findIntentsByContext`](ref/DesktopAgent#findintentsbycontext)
- [`getCurrentChannel`](ref/DesktopAgent#getcurrentchannel)
- [`getInfo`](ref/DesktopAgent#getinfo)
- [`getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel)
- [`getUserChannels`](ref/DesktopAgent#getuserchannels)
- [`open`](ref/DesktopAgent#open)
- [`raiseIntent`](ref/DesktopAgent#raiseintent)
- [`raiseIntentForContext`](ref/DesktopAgent#raiseintentforcontext)
- Provide an ID for each [`PrivateChannel`](ref/PrivateChannel) created via [`createPrivateChannel`](ref/DesktopAgent#createprivatechannel) and prevent them from being retrieved via [`getOrCreateChannel`](ref/DesktopAgent#getorcreatechannel) by ID.
- Only require app directories that they connect to to have implemented only the minimum requirements specified in the [App Directory API Part](../app-directory/spec) of this Standard.
- Provide details of whether they implement optional features of the Desktop Agent API in the `optionalFeatures` property of the [`ImplementationMetadata`](ref/Metadata#implementationmetadata) object returned by the [`fdc3.getInfo()`](ref/DesktopAgent#getinfo) function.
An FDC3 Standard compliant Desktop Agent implementation **SHOULD**:
- Support connection to one or more App Directories meeting the [FDC3 App Directory Standard](../app-directory/overview).
- Qualify `appId` values received from an app directory with the hostname of the app directory server (e.g. `myAppId@name.domain.com`) [as defined in the app directory standard](../app-directory/overview#application-identifiers).
- Adopt the [recommended set of User channel definitions](#recommended-user-channel-set).
- 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.
- Make metadata about each context message or intent and context message received (including the app that originated the message) available to the receiving application.
- Prevent external apps from listening or publishing on a [`PrivateChannel`](ref/PrivateChannel) that they did not request or provide.
- Enforce compliance with the expected behavior of intents (where Intents specify a contract that is enforceable by schema, for example, return object types) and return an error if the interface is not met.
An FDC3 Standard compliant Desktop Agent implementation **MAY**:
- Make the Desktop Agent API available through modules, imports, or other means.
- Implement the following OPTIONAL [Desktop Agent](ref/DesktopAgent) API functions:
- [`joinUserChannel`](ref/DesktopAgent#joinuserchannel)
- [`leaveCurrentChannel`](ref/DesktopAgent#leavecurrentchannel)
- [`getCurrentChannel`](ref/DesktopAgent#getcurrentchannel)
- Implement the following deprecated API functions:
- [`addContextListener`](ref/DesktopAgent#addcontextlistener-deprecated) (without a contextType argument)
- [`getSystemChannels`](ref/DesktopAgent#getsystemchannels-deprecated) (renamed getUserChannels)
- [`joinChannel`](ref/DesktopAgent#joinchannel-deprecated) (renamed joinUserChannel)
- [`open`](ref/DesktopAgent#open-deprecated) (deprecated version that addresses apps via `name` field)
- [`raiseIntent`](ref/DesktopAgent#raiseintent-deprecated) (deprecated version that addresses apps via `name` field)
- [`raiseIntentForContext`](ref/DesktopAgent#raiseintentforcontext-deprecated) (deprecated version that addresses apps via `name` field)
For more details on FDC3 Standards compliance (including the versioning, deprecation and experimental features policies) please see the [FDC3 Compliance page](../fdc3-compliance).
## Functional Use Cases
### Open an Application
Linking from one application to another is a critical basic workflow that the web revolutionized via the hyperlink. Supporting semantic addressing of applications across different technologies and platform domains greatly reduces friction in linking different applications into a single workflow.
### Requesting Functionality From Another App
Often, we want to link from one app to another to dynamically create a workflow. Enabling this without requiring prior knowledge between apps is a key goal of FDC3 and is implemented via the raising of [intents](../intents/spec), which represent a desired action, to be performed with a [context](../context/spec) supplied as input.
Intents provide a way for an app to request functionality from another app and defer the discovery and launching of the destination app to the Desktop Agent. There are multiple models for interop that intents can support.
- **Chain**: In this case the workflow is completely handed off from one app to another (similar to linking). Currently, this is the primary focus in FDC3.
- **Client-Service**: A Client invokes a Service via the Intent, the Service performs some function, then passes the workflow back to the Client. Typically, there is a data payload type associated with this intent that is published as the standard contract for the intent.
- **Remote API**: An app wants to remote an entire API that it owns to another App. In this case, the API for the App cannot be standardized. However, the FDC3 API can address how an App connects to another App in order to get access to a proprietary API.
### Send/broadcast Context
On the financial desktop, applications often want to broadcast [context](../context/spec) to any number of applications. Context sharing needs to support different groupings of applications, which is supported via the concept of 'channels', over which context is broadcast and received by other applications listening to the channel.
In some cases, an application may want to communicate with a single application or service and to prevent other applications from participating in the communication. For single transactions, this can instead be implemented via a raised intent, which will be delivered to a single application that can, optionally, respond with data. Alternatively, it may instead respond with a [`Channel`](ref/Channel) or [`PrivateChannel`](ref/PrivateChannel) over which a stream of responses or a dialog can be supported.
### Retrieve Metadata about the Desktop Agent implementation
An application may wish to retrieve information about the version of the FDC3 Standard supported by a Desktop Agent implementation and the name of the implementation provider.
Since version 1.2 of the FDC3 Standard it may do so via the [`fdc3.getInfo()`](ref/DesktopAgent#getinfo) function. The metadata returned can be used, for example, to vary the behavior of an application based on the version supported by the Desktop Agent, e.g.:
```js
import {compareVersionNumbers, versionIsAtLeast} from '@finos/fdc3';
if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), '1.2')) {
await fdc3.raiseIntentForContext(context);
} else {
await fdc3.raiseIntent('ViewChart', context);
}
```
The [`ImplementationMetadata`](ref/Metadata#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;
```
### Reference apps or app instance(s) and retrieve their metadata
To construct workflows between applications, you need to be able to reference specific applications and instances of those applications.
From version 2.0 of the FDC3 Standard, Desktop Agent functions that reference or return information about other applications do so via an [`AppIdentifier`](ref/Types#appidentifier) type. [`AppIdentifier`](ref/Types#appidentifier) references specific applications via an `appId` from an [App Directory](../app-directory/overview) record and instances of that application via an `instanceId` assigned by the Desktop Agent.
Additional metadata for an application can be retrieved via the [`fdc3.getAppMetadata(appIdentifier)`](ref/DesktopAgent#getappmetadata) function, which returns an [`AppMetadata`](ref/Metadata#appmetadata) object. The additional metadata may include a title, description, icons, etc., which may be used for display purposes.
Identifiers for instances of an application may be retrieved via the [`fdc3.findInstances(appIdentifier)`](ref/DesktopAgent#findinstances) function.
## Raising Intents
Raising an Intent is a method for an application to request functionality from another application and, if desired, defer the discovery and launching of the destination app to the Desktop Agent.
### Intents and Context
When raising an intent a specific context is provided as input. The type of the provided context may determine which applications can resolve the intent.
A context type may also be associated with multiple intents. For example, an `fdc3.instrument` could be associated with `ViewChart`, `ViewNews`, `ViewAnalysis` or other intents.
To raise an intent without a context, use the [`fdc3.nothing`](../context/ref/Nothing) context type. This type exists so that applications can explicitly declare that they support raising an intent without a context (when registering an `IntentHandler` or in an App Directory).
As an alternative to raising a specific intent, you may also raise an unspecified intent with a known context allowing the Desktop Agent or the user (if the intent is ambiguous) to select the appropriate intent and then to raise it with the specified context for resolution.
### Intent Results
An optional [`IntentResult`](ref/Types#intentresult) may also be returned as output by an application handling an intent. Results maybe either a single `Context` object, or a `Channel` that may be used to send a stream of responses. The [`PrivateChannel`](ref/PrivateChannel) type is provided to support synchronisation of data transmitted over returned channels, by allowing both parties to listen for events denoting subscription and unsubscription from the returned channel. `PrivateChannels` are only retrievable via [raising an intent](ref/DesktopAgent#raiseintent).
For example, an application handling a `CreateOrder` intent might return a context representing the order and including an ID, allowing the application that raised the intent to make further calls using that ID.
An optional result type is also supported when programmatically resolving an intent via [`findIntent`](ref/DesktopAgent#findintent) or [`findIntentByContext`](ref/DesktopAgent#findintentbycontext).
### Resolvers
Successful delivery of an intent depends first upon the Desktop Agent's ability to "resolve the intent" (i.e. map the intent to a specific App instance). Where the target application is ambiguous (because there is more than one application that could resolve the intent and context) Desktop Agents may resolve intents by any suitable methodology. A common method is to display a UI that allows the user to pick the desired App from a list of those that will accept the intent and context. Alternatively, the app issuing the intent may proactively handle resolution by calling [`findIntent`](ref/DesktopAgent#findintent) or [`findIntentByContext`](ref/DesktopAgent#findintentbycontext) and then raise the intent with a specific target application, e.g.:
```js
// Find apps to resolve an intent to start a chat with a given contact
const appIntent = await fdc3.findIntent("StartChat", context);
// use the returned AppIntent object to target one of the returned
// chat apps or app instances using the AppMetadata object
await fdc3.raiseIntent("StartChat", context, appIntent.apps[0]);
//Find apps to resolve an intent and return a specified context type
const appIntent = await fdc3.findIntent("ViewContact", context, "fdc3.contact");
try {
const resolution = await fdc3.raiseIntent(appIntent.intent, context, appIntent.apps[0].name);
const result = await resolution.getResult();
console.log(`${resolution.source} returned ${JSON.stringify(result)}`);
} catch(error) {
console.error(`${resolution.source} returned a result error: ${error}`);
}
//Find apps to resolve an intent and return a channel
const appIntent = await fdc3.findIntent("QuoteStream", context, "channel");
try {
const resolution = await fdc3.raiseIntent(appIntent.intent, context, appIntent.apps[0].name);
const result = await resolution.getResult();
if (result && result.addContextListener) {
result.addContextListener(null, (context) => {
console.log(`received context: ${JSON.stringify(context)}`);
});
} else {
console.log(`${resolution.source} didn't return a channel! Result: ${JSON.stringify(result)}`);
}
} catch(error) {
console.error(`${resolution.source} returned a result error: ${error}`);
}
//Find apps that can perform any intent with the specified context
const appIntents = await fdc3.findIntentByContext(context);
//use the returned AppIntent array to target one of the returned apps
await fdc3.raiseIntent(appIntent[0].intent, context, appIntent[0].apps[0]);
```
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<contextType>"`, e.g. `"channel<fdc3.instrument>"`. 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.
For example, to raise a specific intent:
```js
try {
const resolution = await fdc3.raiseIntent('StageOrder', context);
}
catch (err){ ... }
```
or to raise an unspecified intent for a specific context, where the user may select an intent from a resolver dialog:
```js
try {
const resolution = await fdc3.raiseIntentForContext(context);
if (resolution.data) {
const orderId = resolution.data.id;
}
}
catch (err){ ... }
```
Use metadata about the resolving app instance to target a further intent
```js
try {
const resolution = await fdc3.raiseIntent('StageOrder', context);
...
//some time later
await agent.raiseIntent("UpdateOrder", context, resolution.source);
}
catch (err) { ... }
```
Raise an intent and retrieve either data or a channel from the IntentResolution:
```js
let resolution = await agent.raiseIntent("intentName", context);
try {
const result = await resolution.getResult();
/* Detect whether the result is Context or a Channel by checking for properties unique to Channels. */
if (result && result.broadcast) {
console.log(`${resolution.source} returned a channel with id ${result.id}`);
} else if (result){
console.log(`${resolution.source} returned data: ${JSON.stringify(result)}`);
} else {
console.error(`${resolution.source} didn't return anything`);
}
} catch(error) {
console.error(`${resolution.source} returned a data error: ${error}`);
}
```
### 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.
### Originating App Metadata
Optional metadata about each intent & context message received, including the app that originated the message, SHOULD be provided by the desktop agent implementation to registered intent handlers. As this metadata is optional, apps making use of it MUST handle cases where it is not provided.
### 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.
## Context Channels
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).
> **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)
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.
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 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 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
To find a User channel, one calls:
```js
// returns an array of channels
const allChannels = await fdc3.getUserChannels();
const redChannel = allChannels.find(c => c.id === 'red');
```
To join a User channel, one calls:
```js
fdc3.joinUserChannel(redChannel.id);
```
Calling `fdc3.broadcast` will now route context to the joined channel.
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.
> 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). 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.
### Recommended User Channel Set
Desktop Agent implementations SHOULD use the following set of channels, to enable a consistent user experience across different implementations. Desktop Agent implementation MAY support configuration of the user channels.
> Note: Future versions of the FDC3 Standard may support connections between desktop agents, where differing user channel sets may cause user experience issues.
```javascript
const recommendedChannels = [
{
id: 'fdc3.channel.1',
type: 'user',
displayMetadata: {
name: 'Channel 1',
color: 'red',
glyph: '1',
},
},
{
id: 'fdc3.channel.2',
type: 'user',
displayMetadata: {
name: 'Channel 2',
color: 'orange',
glyph: '2',
},
},
{
id: 'fdc3.channel.3',
type: 'user',
displayMetadata: {
name: 'Channel 3',
color: 'yellow',
glyph: '3',
},
},
{
id: 'fdc3.channel.4',
type: 'user',
displayMetadata: {
name: 'Channel 4',
color: 'green',
glyph: '4',
},
},
{
id: 'fdc3.channel.5',
type: 'user',
displayMetadata: {
name: 'Channel 5',
color: 'cyan',
glyph: '5',
},
},
{
id: 'fdc3.channel.6',
type: 'user',
displayMetadata: {
name: 'Channel 6',
color: 'blue',
glyph: '6',
},
},
{
id: 'fdc3.channel.7',
type: 'user',
displayMetadata: {
name: 'Channel 7',
color: 'magenta',
glyph: '7',
},
},
{
id: 'fdc3.channel.8',
type: 'user',
displayMetadata: {
name: 'Channel 8',
color: 'purple',
glyph: '8',
},
},
];
```
### 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.
### App Channels
App Channels are topics dynamically created by applications connected via FDC3. For example, an app may create a named App Channel to broadcast data or status that is specific to that app to other apps that know to connect to the channel with that name.
To get (or create) a channel reference, then interact with it:
```js
const appChannel = await fdc3.getOrCreateChannel('my_custom_channel');
// get the current context of the channel
const current = await appChannel.getCurrentContext();
// add a listener
await appChannel.addContextListener(null, context => {...});
// broadcast to the channel
await appChannel.broadcast(context);
```
An app can still explicitly receive context events on any channel, regardless of the channel it is currently joined to.
```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
//add a context listener for channels we join
const listener = await fdc3.addContextListener(null, context => { ... });
//retrieve an App channel and add a listener that is specific to that channel
const myChannel = await fdc3.getOrCreateChannel('my_custom_channel');
const myChannelListener = await myChannel.addContextListener(null, context => { ... });
fdc3.joinChannel('blue')
joinedChannel = await fdc3.getCurrentChannel()
//current channel is now the 'blue' channel
```
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`).
### Private Channels
`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.
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.
### Originating App Metadata
Optional metadata about each context message received, including the app that originated the message, SHOULD be provided by the desktop agent implementation to registered context handlers on all types of channel. As this metadata is optional, apps making use of it MUST handle cases where it is not provided.