Desktop Agent Communication Protocol (next)
FDC3's Desktop Agent Communication Protocol (DACP) is an experimental feature added to FDC3 in 2.2. Limited aspects of its design may change in future versions and it is exempted from the FDC3 Standard's normal versioning and deprecation polices in order to facilitate any necessary change.
The Desktop Agent Communication Protocol (DACP) constitutes a set of standardized JSON messages or 'wire protocol' that can be used to implement an interface to a Desktop Agent, encompassing all API calls events defined in the Desktop Agent API. For example, the DACP is used by the @finos/fdc3
npm module to communicate with Browser-Resident Desktop Agents or a connection setup via the FDC3 Web Connection Protocol.
Protocol conventions
DACP messages are defined in JSON Schema in the FDC3 github repository.
TypeScript types representing all DACP and WCP messages are generated from the JSON Schema source and can be imported from the @finos/fdc3
npm module:
import { BrowserTypes } from "@finos/fdc3";
The protocol is composed of several different classes of message, each governed by a message schema:
App Request Messages (
AppRequest
schema):- Messages sent by an application representing an API call, such as
DesktopAgent.broadcast
,Channel.addContextListener
, orListener.unsubscribe
. - Message names all end in 'Request'.
- Each instance of a request message sent is uniquely identified by a
meta.requestUuid
field.
- Messages sent by an application representing an API call, such as
Agent Response Messages (
AgentResponse
schema):- Response messages sent from the DA to the application, each relating to a corresponding App Request Message.
- Message names all end in 'Response'.
- Each instance of an Agent Response Message is uniquely identified by a
meta.responseUuid
field. - Each instance of an Agent Response Message quotes the
meta.requestUuid
value of the message it is responding to.
Agent Event Messages (
AgentEvent
schema):- Messages sent from the DA to the application that are due to actions in other applications, such as an inbound context resulting from another app's broadcast.
- Message names all end in 'Event'.
- Each instance of an Agent Response Message is uniquely identified by a
meta.eventUuid
field.
Each individual message is also governed by a message schema, which is composed with the schema for the message type.
In rare cases, the payload of a request or event message may quote the requestUuid
or eventUuid
of another message that it represents a response to, e.g. intentResultRequest
quotes the eventUuid
of the intentEvent
that delivered the intent and context to the app, as well as the requestUuid
of the raiseIntentRequest
message that originally raised the intent.
All messages defined in the DACP follow a common structure:
{
"type": "string", // string identifying the message type
"payload": {
//message payload fields defined for each message type
},
"meta": {
"timestamp": "2024-09-17T10:15:39+00:00"
//other meta fields determined by each 'class' of message
// these include requestUuid, responseUuid and eventUuid
// and a source field identifying an app where appropriate
}
}
meta.timestamp
fields are formatted as strings, according to the format defined by ISO 8601-1:2019, which is produced in JavaScript via the Date
class's toISOString()
function, e.g. (new Date()).toISOString()
.
Routing, Registering Listeners & Multiplexing
The design of the Desktop Agent Communication Protocol is guided by the following sentence from the introduction to the Desktop Agent overview:
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.
Hence, that design is based on the assumption that all messaging between applications passes through an entity that acts as the 'Desktop Agent' and routes those messages on to the appropriate recipients (for example, a context message broadcast by an app to a channel is routed onto other apps that have added a listener to that channel, or an intent and context pair raised by an application is routed to another app chosen to resolve that intent). While implementations based on a shared bus are possible, they have not been specifically considered in the design of the DACP messages.
Further, the design of the DACP is based on the assumption that applications will interact with an implementation of the DesktopAgent
interface, with the DACP used behind the scenes to support communication between the implementation of that interface and an entity acting as the Desktop Agent which is running in another process or location, necessitating the use of a 'wire protocol' for communication. For example, Browser-Resident Desktop Agent implementations use the FDC3 Web Communication Protocol (WCP) to connect a 'Desktop Agent Proxy', provided by the getAgent()
implementation in the @finos/fdc3
npm module, and a Desktop Agent running in another frame or window which is communicated with via the DACP.
As a Desktop Agent is expected to act as a router for messages sent through the Desktop Agent API, the DACP provides message exchanges for the registration and un-registration of listeners for particular message types (e.g. events, contexts broadcast on user channels, contexts broadcast on other channel types, raised intents etc.). In most cases, apps can register multiple listeners for the same messages (often filtered for different context or event types). However, where multiple listeners are present, only a single DACP message should be sent representing the action taken in the FDC3 API (e.g. broadcasting a message to a channel) and any multiplexing to multiple listeners should be applied at the receiving end. For example, when working with the WCP, this should be handled by the Desktop Agent Proxy implementation provided by the getAgent()
implementation.
Message Definitions Supporting FDC3 API calls
This section provides details of the messages defined in the DACP, grouped according to the FDC3 API functions that they support, and defined by JSON Schema files. Many of these message definitions make use of JSON versions of metadata and other types defined by the Desktop Agent API, the JSON versions of which can be found in api.schema.json, while a number of DACP specific object definitions that are reused through the messages can be found in common.schema.json.
DesktopAgent
addContextListener()
Request and response used to implement the DesktopAgent.addContextListener()
and Channel.addContextListener()
API calls:
Event message used to deliver context objects that have been broadcast to listeners:
Request and response for removing the context listener (Listener.unsubscribe()
):
addEventListener()
Request and response used to implement the addEventListener()
API call:
Event messages used to deliver events that have occurred:
Request and response for removing the event listener (Listener.unsubscribe()
):
addIntentListener()
Request and response used to implement the addIntentListener()
API call:
Event message used to a raised intent and context object from another app to the listener:
An additional request and response used to deliver an IntentResult
from the intent handler to the Desktop Agent, so that it can convey it back to the raising application:
Please note this exchange (and the IntentResolution.getResult()
API call) support void
results from a raised intent and hence this message exchange should occur for all raised intents, including those that do not return a result. In such cases, the void intent result allows resolution of the IntentResolution.getResult()
API call and indicates that the intent handler has finished running.
Request and response for removing the intent listener (Listener.unsubscribe()
):
A typical exchange of messages between an app raising an intent, a Desktop agent and an app resolving an intent is:
The above flow assumes that AppB has already been launched and added an intent listener. As apps can be launched to resolve an intent a typical message exchange (that includes registration of the intent listener) is:
See raiseIntent
below for further examples of message exchanges involved in raising intents and intent resolution.
broadcast()
Request and response used to implement the DesktopAgent.broadcast()
and Channel.broadcast()
API calls:
See addContextListener()
above for the broadcastEvent
used to deliver the broadcast to other apps.
createPrivateChannel()
Request and response used to implement the createPrivateChannel()
API call:
findInstances()
Request and response used to implement the findInstances()
API call:
findIntent()
Request and response used to implement the findIntent()
API call:
findIntentsByContext()
Request and response used to implement the findIntentsByContext()
API call:
getAppMetadata()
Request and response used to implement the getAppMetadata()
API call:
getCurrentChannel()
Request and response used to implement the getCurrentChannel()
API call:
getInfo()
Request and response used to implement the getInfo()
API call:
getOrCreateChannel()
Request and response used to implement the getOrCreateChannel()
API call:
getUserChannels()
Request and response used to implement the getUserChannels()
API call:
joinUserChannel()
Request and response used to implement the joinUserChannel()
API call:
leaveCurrentChannel()
Request and response used to implement the leaveCurrentChannel()
API call:
open()
Request and response used to implement the open()
API call:
Where a context object is passed (e.g. fdc3.open(app, context)
) the broadcastEvent
message described above in addContextListener
should be used to deliver it after the context listener has been added:
However, if the app opened doesn't add a context listener within a timeout (defined by the Desktop Agent) then the openResponse
should be sent with AppTimeout
error from the OpenError
enumeration.
Desktop Agents MUST allow at least 15 seconds for an app to add a context listener before timing out (see Desktop Agent API Standard Compliance for more detail) and applications SHOULD add their listeners as soon as possible to keep the delay short (see the addContextListener reference doc).
raiseIntent()
Request and response used to implement the raiseIntent()
API call:
An additional response message is provided for the delivery of an IntentResult
from the resolving application to the raising application (which is collected via the IntentResolution.getResult()
API call), which should quote the requestUuid
from the original raiseIntentRequest
:
There is no request message to indicate a call to the resolution.getResult()
function of IntentResolution
. Hence, Desktop Agents MUST send this additional response message to indicate the status of the intent handling function and to deliver its result (or void if none was returned).
See addIntentListener
above for details of the messages used for the resolving app to deliver the result to the Desktop Agent.
Where there are multiple options for resolving a raised intent, there are two possible versions of the resulting message exchanges. Which to use depends on whether the Desktop Agent uses an intent resolver user interface (or other suitable mechanism) that it controls, or one injected into the application (for example an iframe injected by a getAgent()
implementation into an application window) to perform resolution.
When working with an injected interface, the Desktop Agent should respond with a raiseIntentResponse
containing a RaiseIntentNeedsResolutionResponsePayload
:
Alternatively, if the Desktop Agent is able to provide its own user interface or another suitable means of resolving the intent, then it may do so and respond with a raiseIntentResponse
containing a RaiseIntentSuccessResponsePayload
:
raiseIntentForContext()
Request and response used to implement the raiseIntentForContext()
API call:
Message exchanges for handling raiseIntentForContext()
are the same as for raiseIntent
, except for the substitution of raiseIntentForContextRequest
for raiseIntentRequest
and raiseIntentForContextResponse
for raiseIntentResponse
. Hence, please see raiseIntent
and addIntentListener
for further details.
Channel
Owing to the significant overlap between the FDC3 DesktopAgent
and Channel
interfaces, which includes the ability to retrieve and work with User channels as App Channels, most of the messaging for the Channel
API is shared with DesktopAgent
. Specifically, all messages defined in the the broadcast
and addContextListener
sections above are reused, with a few minor differences to note:
- When working with a specific channel, the
channelId
property inaddContextListenerRequest
should be set to the ID of the channel, where it is set tonull
to work with the current user channel. - When receiving a
broadcastEvent
achannelId
that isnull
indicates that the context was sent via a call tofdc3.open
and does not relate to a channel.
The following additional function is unique to the Channel
interface:
getCurrentContext()
Request and response used to implement the Channel.getCurrentContext()
API call:
PrivateChannel
The PrivateChannel
interface extends Channel
with a number of additional functions that are supported by the following messages:
addEventListener()
Request and response used to implement the PrivateChannel.addEventListener
API call:
Event messages used to deliver events that have occurred:
privateChannelOnAddContextListenerEvent
privateChannelOnDisconnectEvent
privateChannelOnUnsubscribeEvent
The above messages may also be used to implement the deprecated onAddContextListener()
, onUnsubscribe
and onDisconnect
functions of the PrivateChannel
interface.
Message exchange for removing the event listener Listener.unsubscribe
:
disconnect()
Request and response used to implement the PrivateChannel.disconnect()
API call:
Checking apps are alive
Depending on the connection over which the Desktop Agent and app are connected, it may be necessary for the Desktop Agent to check whether the application is still alive. This can be done, either periodically or on demand (for example to validate options that will be provided in an AppIntent
as part of a findIntentResponse
or raiseIntentResponse
and displayed in an intent resolver interface), using the following message exchange:
As a Desktop Agent initiated exchange, it is initiated with an AgentEvent
message and completed via an AppRequest
message as an acknowledgement.
Additional procedures are defined in the Browser Resident Desktop Agents specification and Web Connection Protocol for the detection of app disconnection or closure. Implementations will often need to make use of multiple procedures to catch all forms of disconnection in a web browser.
Controlling injected User Interfaces
Desktop Agent implementations, such as those based on the Browser Resident Desktop Agents specification and Web Connection Protocol, may either provide their own user interfaces (or other appropriate mechanisms) for the selection of User Channels or Intent Resolution, or they may work with implementations injected into the application (for example, as described in the Web Connection Protocol and implemented in getAgent()
).
Where injected user interfaces are used, standardized messaging is needed to communicate with those interfaces. This is provided in the DACP via the following 'iframe' messages, which are governed by the Fdc3UserInterfaceMessage
schema. The following messages are provided:
Fdc3UserInterfaceHello
: Sent by the iframe to itswindow.parent
frame to initiate communication and to provide initial CSS to apply to the frame. This message should have aMessagePort
appended over which further communication will be conducted.Fdc3UserInterfaceHandshake
: Response to theFdc3UserInterfaceHello
message sent by the application frame, which should be sent over theMessagePort
. Includes details of the FDC3 version that the application is using.Fdc3UserInterfaceDrag
: Message sent by the iframe to indicate that it is being dragged to a new position and including offsets to indicate direction and distance.Fdc3UserInterfaceRestyle
: Message sent by the iframe to indicate that its frame should have updated CSS applied to it, for example to support a channel selector interface that can be 'popped open' or an intent resolver that wishes to resize itself to show additional content.
Messages are also provided that are specific to each user interface type provided by a Desktop Agent. The following messages are specific to Channel Selector user interfaces:
Fdc3UserInterfaceChannels
: Sent by the parent frame to initialize a Channel Selector user interface by providing metadata for the Desktop Agent's user channels and details of any channel that is already selected. This message will typically be sent by agetAgent()
implementation immediately after thefdc3UserInterfaceHandshake
and before making the injected iframe visible.Fdc3UserInterfaceChannelSelected
: Sent by the Channel Selector to indicate that a channel has been selected or deselected.
Messages specific to Intent Resolver user interfaces:
Fdc3UserInterfaceResolve
: Sent by the parent frame to initialize an Intent Resolver user interface to resolve a raised intent, before making the iframe visible. The message includes the context object sent with the intent and an array of one or moreAppIntent
objects representing the resolution options for the intent (raiseIntent
) or context (raiseIntentForContext
) that was raised.Fdc3UserInterfaceResolveAction
: Sent by the Intent Resolver to indicate actions taken by the user in the interface, including hovering over an option, clicking a cancel button, or selecting a resolution option. The Intent Resolver should be hidden by thegetAgent()
implementation after a resolution option is selected to ensure that it does not interfere with user's ongoing interaction with the app's user interface.