diff --git a/docs/api-bridging/spec.md b/docs/api-bridging/spec.md index 747e12663..54a86dc4f 100644 --- a/docs/api-bridging/spec.md +++ b/docs/api-bridging/spec.md @@ -687,8 +687,8 @@ Type: **Request only** Generated by API calls: -* [`fdc3.broadcast(contextObj)`](../api/ref/DesktopAgent#broadcast) -* [`Channel.broadcast(contextObj)`](../api/ref/Channel#broadcast) +* [`fdc3.broadcast(context: Context)`](../api/ref/DesktopAgent#broadcast) +* [`Channel.broadcast(context: Context)`](../api/ref/Channel#broadcast) e.g. @@ -765,7 +765,7 @@ Type: **Request Response (collated)** Generated by API call: -* [`findIntent(intent, context)`](../api/ref/DesktopAgent#findintent) +* [`findIntent(intent: string, context: Context)`](../api/ref/DesktopAgent#findintent) e.g. An application with `appId: "agentA-app1"` and `instanceId: "c6ad5174-6f78-4582-8e96-728d93a4d7d7"` makes the following API call: @@ -995,7 +995,7 @@ Type: **Request Response (collated)** Generated by API call: -* [`findIntentsByContext(context)`](../api/ref/DesktopAgent#findintentsbycontext) +* [`findIntentsByContext(context: Context)`](../api/ref/DesktopAgent#findintentsbycontext) e.g. An application with appId `agentA-app1` makes the following API call: @@ -1161,7 +1161,10 @@ Each `AppMetadata` object is augmented by the bridge with a `desktopAgent` field "requestGuid": "", "responseGuid": "", "timestamp": "2020-03-...", - "sources": [{ "desktopAgent": "agent-B" }, { "desktopAgent": "agent-C" }] + "sources": [ + { "desktopAgent": "agent-B" }, + { "desktopAgent": "agent-C" } + ] } } ``` @@ -1174,7 +1177,7 @@ Type: **Request Multiple Response (single)** Generated by API call: -* [`raiseIntent(intent, context, app)`](../api/ref/DesktopAgent#raiseIntent) +* [`raiseIntent(intent: string, context: Context, app: AppIdentifier)`](../api/ref/DesktopAgent#raiseIntent) For Desktop Agent Bridging, a `fdc3.raiseIntent` call MUST always pass a `app: AppIdentifier` argument to target the intent. If a target `app` is not passed, then the `findIntent` message exchange should be used to collect options for the local resolver to use (note that Desktop Agents MAY also support the deprecated `raiseIntent` signature that uses the app `name` field by using the `findIntent` message exchange to attempt to resolve the `name` to an `AppIdentifier`). Once an option has been selected (for example because there is only one option, or because the user selected an option in a local intent resolver UI), the `raiseIntent` message exchange may then be used (if a remote option was selected as the resolution) to raise the intent. @@ -1204,10 +1207,10 @@ sequenceDiagram participant DC as Desktop Agent C DA ->>+ DAB: raiseIntent DAB ->>+ DB: raiseIntent - DB -->>- DAB: intentResolution - DAB -->>- DA: intentResolution - DB ->>+ DAB: intentResult - DAB ->>+ DA: intentResult + DB -->>- DAB: raiseIntentResponse + DAB -->>- DA: raiseIntentResponse + DB ->>+ DAB: raiseIntentResultResponse + DAB ->>+ DA: raiseIntentResultResponse ``` #### Request format @@ -1235,10 +1238,8 @@ Outward message to the DAB: "instanceId": "c6ad5174-6f78-4582-8e96-728d93a4d7d7" }, "destination": { // duplicates the app argument so that the message is routed like any other - "app": { - "appId": "Slack", - "desktopAgent": "agent-B" - } + "appId": "Slack", + "desktopAgent": "agent-B" } } } @@ -1267,12 +1268,10 @@ The bridge fills in the `source.desktopAgent` field and forwards the request to "instanceId": "c6ad5174-6f78-4582-8e96-728d93a4d7d7", "desktopAgent": "agent-A" //added by DAB }, - "destination": { - "app": { - "appId": "Slack", - "desktopAgent": "agent-B" - } - }, + "destination": { // duplicates the app argument so that the message is routed like any other + "appId": "Slack", + "desktopAgent": "agent-B" + } } } ``` @@ -1427,54 +1426,40 @@ If the `IntentHandler` returned `void` rather than an intent result `payload.int -//TODO: further work required on the below messsage exchanges: -### open - -```typescript -open(app: TargetApp, context?: Context): Promise; -``` -When receiving a response from invoking `fdc3.open` the new app instances MUST be fully initialized ie. the responding Desktop Agent will need to return an `AppIdentifier` with an `instanceId`. +### open (on `fdc3`) -#### Request format +Type: **Request Response (single)** -A `fdc3.open` call is made on agent-A. +Generated by API call: -```javascript -// Open an app without context, using the app name -let instanceMetadata = await fdc3.open('myApp'); +* [`open(app: AppIdentifier, context?: Context)`](../api/ref/DesktopAgent#open) -// Open an app without context, using an AppIdentifier object to specify the target -let AppIdentifier = {name: 'myApp', appId: 'myApp-v1.0.1', version: '1.0.1'}; -let instanceMetadata = await fdc3.open(AppIdentifier); +e.g. +```javascript // Open an app without context, using an AppIdentifier object to specify the target and Desktop Agent -let AppIdentifier = {name: 'myApp', appId: 'myApp-v1.0.1', version: '1.0.1', desktopAgent:"DesktopAgentB"}; +let AppIdentifier = {appId: 'myApp-v1.0.1', desktopAgent:"DesktopAgentB"}; let instanceMetadata = await fdc3.open(AppIdentifier); -``` -The `fdc3.open` command should result in a single copy of the specified app being opened and its instance data returned, or an error if it could not be opened. There are two possible scenarios: +// Open an app with context, using an AppIdentifier object to specify the target and Desktop Agent +let AppIdentifier = {appId: 'myApp-v1.0.1', desktopAgent:"DesktopAgentB"}; +let instanceMetadata = await fdc3.open(AppIdentifier, contextObj); +``` -1) The Desktop Agent that the app should open on is specified -2) The Desktop Agent that the app should open on is NOT specified app +Note that it is not currently possible to identify resolve all available applications within a Desktop Agent via the FDC3 API. Hence, `fdc3.open` calls without a specified `desktopAgent` field in their `AppIdentifier`, e.g.: -The first case (target Desktop Agent is specified) is simple: +```javascript +// Open a target app via AppIdentifier, without a specified Desktop Agent +let AppIdentifier = {appId: 'myApp-v1.0.1'}; +let instanceMetadata = await fdc3.open(AppIdentifier); +``` -* If the local Desktop Agent is the target, handle the call normally -* Otherwise: - * Request is sent to the bridge - * DAB checks to see if any of the connected DAs is the target and transmit the call to it and awaits a response - * otherwise return `OpenError.AppNotFound` +should always be processed locally without be passed to the bridge. -The second case is a little trickier as we don't know which agent may have the app available: +The `fdc3.open` command should result in a single copy of the specified app being opened and its instance data returned, or an error if it could not be opened. When receiving a response from invoking `fdc3.open` via the Desktop Agent Bridge, the new app instances MUST be initialized before responding as the responding Desktop Agent will need to return an `AppIdentifier` with an `instanceId` field set. -* If the local Desktop Agent has the app, open it and exit. -* Otherwise: - * Request is sent to the bridge - * Bridge will query each connected DA asynchronously and await a response - * If the response is `AppIdentifier` then return it and exit (ignore every subsequent response) - * If the response is `OpenError.AppNotFound` and there are pending responses, wait for the next response - * If the response is `OpenError.AppNotFound` and there are NO pending responses, return `OpenError.AppNotFound` +Message exchange: ```mermaid sequenceDiagram @@ -1482,34 +1467,15 @@ sequenceDiagram participant DAB as Desktop Agent Bridge participant DB as Desktop Agent B participant DC as Desktop Agent C - DA ->>+ DAB: Open Chart - DAB ->>+ DB: Open Chart - DAB ->>+ DC: Open Chart - DB ->> DB: App NOT Found - DB -->>- DAB: OpenError.AppNotFound - DC ->> DC: App Found - DC ->> DC: Open App - DC -->>- DAB: Return App Data - DAB -->>- DA: Augmented App Data + DA ->>+ DAB: open + DAB ->>+ DB: open + DB -->>- DAB: openResponse + DAB -->>- DA: openResponse ``` -__When the target Desktop Agent is set__ - -```mermaid -sequenceDiagram - participant DA as Desktop Agent A - participant DAB as Desktop Agent Bridge - participant DB as Desktop Agent B - participant DC as Desktop Agent C - DA ->>+ DAB: Open App - DAB ->>+ DC: Open App - DC ->> DC: Desktop agent in list and App Found - DC ->>x DC: Open App - DC -->>- DAB: Return App Data - DAB -->>- DA: Return App Data -``` +#### Request format -It sends an outward message to the bridge: +Outward message to the bridge: ```JSON // agent-A -> DAB @@ -1533,7 +1499,7 @@ It sends an outward message to the bridge: } ``` -which is repeated as: +which is repeated on to the target agent as: ```JSON // DAB -> agent-B @@ -1558,26 +1524,224 @@ which is repeated as: } ``` +#### Response format + +Response message from target Desktop Agent: +```JSON +// agent-B -> DAB +{ + "type": "openResponse", + "payload": { + "appIdentifier": { + "appId": "myApp", + "instanceId": "e36d43e1-4fd3-447a-a227-38ec48a92706" + } + }, + "meta": { + "requestGuid": "", + "responseGuid": "", + "timestamp": "2020-03-..." + } +} +``` + +which is augmented and repeated on by the bridge as: + +```JSON +// agent-B -> DAB +{ + "type": "openResponse", + "payload": { + "appIdentifier": { + "appId": "myApp", + "instanceId": "e36d43e1-4fd3-447a-a227-38ec48a92706" + } + }, + "meta": { + "requestGuid": "", + "responseGuid": "", + "timestamp": "2020-03-...", + "sources": [{ "desktopAgent": "agent-B" }] // added by DAB + } +} +``` + + +//TODO: further work required on the below messsage exchanges: ### findInstances -```typescript -findInstances(app: TargetApp): Promise>; +Type: **Request Response (collated)** or **Request Response (single)** + +Generated by API call: + +* [`findInstances(app: AppIdentifier)`](../api/ref/DesktopAgent#findinstances) + +e.g. + +```javascript +// Retrieve a list of all instances of an application +let instances = await fdc3.findInstances({appId: "MyAppId"}); + +// Retrieve a list of instances of an application on a specified Desktop Agent +let instances = await fdc3.findInstances({appId: "MyAppId", desktopAgent: "agent-A"}); ``` +Message exchange: + ```mermaid sequenceDiagram participant DA as Desktop Agent A participant DAB as Desktop Agent Bridge participant DB as Desktop Agent B participant DC as Desktop Agent C - DA ->>+ DAB: Find Instances of App. - DAB ->>+ DB: Find Instances of App - DAB ->>+ DC: Find Instances of App - DC --x DC: No Instance found - DB ->> DB: App Instance found - DB -->>- DA: Return App Data + DA ->>+ DAB: findInstances + DAB ->>+ DB: findInstances + DAB ->>+ DC: findInstances + DB ->> DAB: findInstancesResponse (B) + DC ->> DAB: findInstancesResponse (C) + DAB -->>- DA: findInstancesResponse (B + C) +``` + +#### Request format + +Outward message to the bridge: + +```JSON +// agent-A -> DAB +{ + "type": "findInstances", + "payload": { + "app": { + "appId": "myApp" + } + }, + "meta": { + "requestGuid": "", + "timestamp": "2020-03-...", + "source": { + "appId": "AChatApp", + "instanceId": "02e575aa-4c3a-4b66-acad-155073be21f6" + } + } +} +``` + +which is repeated on to the target agent as: + +```JSON +// DAB -> agent-B +{ + "type": "open", + "payload": { + "app": { + "appId": "myApp", + "desktopAgent":"DesktopAgentB" + }, + "context": {/*contextObj*/} + }, + "meta": { + "requestGuid": "", + "timestamp": 2020-03-..., + "source": { + "appId": "AChatApp", + "instanceId": "02e575aa-4c3a-4b66-acad-155073be21f6", + "desktopAgent": "agent-A" //added by DAB + } + } +} +``` + +If results should be constrained to a particular Desktop Agent, then set a `desktopAgent` field in `payload.app` and a matching `destination` field in `meta`: + +```JSON +// agent-A -> DAB +{ + "type": "findInstances", + "payload": { + "app": { + "appId": "myApp" + "desktopAgent": "agent-B" // destination agent + } + }, + "meta": { + "requestGuid": "", + "timestamp": "2020-03-...", + "destination": { "desktopAgent": "agent-B"}, //destination agent + "source": { + "appId": "AChatApp", + "instanceId": "02e575aa-4c3a-4b66-acad-155073be21f6" + } + } +} +``` + +The Desktop Agent Bridge should only forward the request to the requested Destkop Agent and handle the message exchange as a **Request Response (single)**. + +#### Response format + +Response message from a Desktop Agent: + +```JSON +// agent-B -> DAB +{ + "type": "findInstancesResponse", + "payload": { + "appIdentifiers": [ + { "appId": "myApp", "instanceId": "4bf39be1-a25b-4ad5-8dbc-ce37b436a344"}, + { "appId": "myApp", "instanceId": "4f10abb7-4df4-4fc6-8813-bbf0dc1b393d"}, + ] + }, + "meta": { + "requestGuid": "", + "responseGuid": "", + "timestamp": "2020-03-...", + } +} +``` + +The bridge receives and collates the responses, augmenting each appIdentifier with a `desktopAgent` field, producing the following collated response which it sends back to agent-A: + +```JSON +// DAB -> agent-A +{ + "type": "findIntentResponse", + "payload": { + "appIdentifiers": [ + { "appId": "myApp", "instanceId": "4bf39be1-a25b-4ad5-8dbc-ce37b436a344", "desktopAgent": "agent-B"}, + //"desktopAgent" added by DAB + { "appId": "myApp", "instanceId": "4f10abb7-4df4-4fc6-8813-bbf0dc1b393d", "desktopAgent": "agent-B" }, + { "appId": "myApp", "instanceId": "920b74f7-1fef-4076-adef-63b82bae0dd9", "desktopAgent": "agent-C" }, + ] + }, + "meta": { + "requestGuid": "", + "responseGuid": "", + "timestamp": "2020-03-...", + "sources": [ //added by DAB + { "desktopAgent": "agent-A" }, + { "desktopAgent": "agent-B" }, + ] + } +} ``` +:::note +In the event that an agent times out or returns an error, where others respond, its `DesktopAgentIdentifier` should be added to the `meta.errorSources element` instead of `meta.sources`. +::: + +Finally, agent-A combines the data received from the bridge, with its own local response to produce the response to the requesting application: + +```JSON +// DAB -> agent-A +[ + { "appId": "myApp", "instanceId": "4bf39be1-a25b-4ad5-8dbc-ce37b436a344", "desktopAgent": "agent-B"}, + { "appId": "myApp", "instanceId": "4f10abb7-4df4-4fc6-8813-bbf0dc1b393d", "desktopAgent": "agent-B" }, + { "appId": "myApp", "instanceId": "920b74f7-1fef-4076-adef-63b82bae0dd9", "desktopAgent": "agent-C" }, + { "appId": "myApp", "instanceId": "688dbd5e-21dc-4469-b8cf-4b6a606f9a27" } //local response +] +``` + + ### PrivateChannels `PrivateChannels` are intended to provide a private communication channel for applications. In order to do so, there are differences in how their broadcasts MUST be handled and a number of additional message exchanges MUST be supported in order to handle events that are used to manage the channel's lifecycle.