Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- resolves ([#1487](https://github.com/finos/FDC3/issues/1487))
- resolves ([#1488](https://github.com/finos/FDC3/issues/1488))
* Adjusted reference Desktop Agent implementation for FDC3 for Web to open a new app instance when raiseIntent is called with an appId but no instanceId ([#1556](https://github.com/finos/FDC3/pull/1556))
* Added `addIntentListenerWithContext` to `DesktopAgent` and implemented in `DesktopAgentProxy`. Added handling of optional `contextTypes` to reference implementation

### Changed

Expand Down
5 changes: 5 additions & 0 deletions packages/fdc3-agent-proxy/src/DesktopAgentProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export class DesktopAgentProxy implements DesktopAgent, Connectable {
this.findIntentsByContext = this.findIntentsByContext.bind(this);
this.raiseIntent = this.raiseIntent.bind(this);
this.addIntentListener = this.addIntentListener.bind(this);
this.addIntentListenerWithContext = this.addIntentListenerWithContext.bind(this);
this.raiseIntentForContext = this.raiseIntentForContext.bind(this);
this.open = this.open.bind(this);
this.findInstances = this.findInstances.bind(this);
Expand Down Expand Up @@ -183,6 +184,10 @@ export class DesktopAgentProxy implements DesktopAgent, Connectable {
return this.intents.addIntentListener(intent, handler);
}

addIntentListenerWithContext(intent: string, contextType: string | string[], handler: IntentHandler) {
return this.intents.addIntentListenerWithContext(intent, contextType, handler);
}

raiseIntentForContext(context: Context, app?: string | AppIdentifier): Promise<IntentResolution> {
return this.intents.raiseIntentForContext(context, this.ensureAppId(app));
}
Expand Down
18 changes: 17 additions & 1 deletion packages/fdc3-agent-proxy/src/intents/DefaultIntentSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,23 @@ export class DefaultIntentSupport implements IntentSupport {
}

async addIntentListener(intent: string, handler: IntentHandler): Promise<Listener> {
const out = new DefaultIntentListener(this.messaging, intent, handler, this.messageExchangeTimeout);
const out = new DefaultIntentListener(this.messaging, intent, undefined, handler, this.messageExchangeTimeout);
await out.register();
return out;
}

async addIntentListenerWithContext(
intent: string,
contextType: string | string[],
handler: IntentHandler
): Promise<Listener> {
const out = new DefaultIntentListener(
this.messaging,
intent,
Array.isArray(contextType) ? contextType : [contextType],
handler,
this.messageExchangeTimeout
);
await out.register();
return out;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/fdc3-agent-proxy/src/intents/IntentSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ export interface IntentSupport {
raiseIntent(intent: string, context: Context, app?: AppIdentifier): Promise<IntentResolution>;
raiseIntentForContext(context: Context, app?: AppIdentifier): Promise<IntentResolution>;
addIntentListener(intent: string, handler: IntentHandler): Promise<Listener>;
addIntentListenerWithContext(
intent: string,
contextType: string | string[],
handler: IntentHandler
): Promise<Listener>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,34 @@ import {
IntentEvent,
IntentResultRequest,
IntentResultResponse,
//RaiseIntentResponse,
} from '@finos/fdc3-schema/dist/generated/api/BrowserTypes';

export class DefaultIntentListener extends AbstractListener<IntentHandler, AddIntentListenerRequest> {
readonly intent: string;

constructor(messaging: Messaging, intent: string, action: IntentHandler, messageExchangeTimeout: number) {
constructor(
messaging: Messaging,
private readonly intent: string,
private readonly contextTypes: string[] | undefined,
action: IntentHandler,
messageExchangeTimeout: number
) {
super(
messaging,
messageExchangeTimeout,
{ intent },
{ intent, contextTypes },
action,
'addIntentListenerRequest',
'addIntentListenerResponse',
'intentListenerUnsubscribeRequest',
'intentListenerUnsubscribeResponse'
);
this.intent = intent;
}

filter(m: IntentEvent): boolean {
return m.type == 'intentEvent' && m.payload.intent == this.intent;
return (
m.type == 'intentEvent' &&
m.payload.intent == this.intent &&
(this.contextTypes == null || this.contextTypes.includes(m.payload.context.type))
);
}

action(m: IntentEvent): void {
Expand Down
32 changes: 32 additions & 0 deletions packages/fdc3-agent-proxy/test/features/intent-listener.feature
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,38 @@ Feature: Intent Listeners
| type |
| intentResultRequest |

Scenario: Intent Listeners With non-matching Context does not handle message
Given "resultHandler" pipes intent to "intents"
When I call "{api1}" with "addIntentListenerWithContext" with parameters "BuyStock" and "fdc3.order" and "{resultHandler}"
And messaging receives "{intentMessageOne}"
Then "{intents}" is an array of objects with the following contents
| context.type | context.name | metadata.source.appId |
And messaging will have posts
| type |

Scenario: Intent Listeners With matching Context string does handle message
Given "resultHandler" pipes intent to "intents"
When I call "{api1}" with "addIntentListenerWithContext" with parameters "BuyStock" and "fdc3.instrument" and "{resultHandler}"
And messaging receives "{intentMessageOne}"
Then "{intents}" is an array of objects with the following contents
| context.type | context.name | metadata.source.appId |
| fdc3.instrument | Apple | some-app-id |
And messaging will have posts
| type |
| intentResultRequest |

Scenario: Intent Listeners With matching Context array does handle message
Given "resultHandler" pipes intent to "intents"
And "contextArray" is an array of contexts including "fdc3.instrument" and "fdc3.instrumentList"
When I call "{api1}" with "addIntentListenerWithContext" with parameters "BuyStock" and "{contextArray}" and "{resultHandler}"
And messaging receives "{intentMessageOne}"
Then "{intents}" is an array of objects with the following contents
| context.type | context.name | metadata.source.appId |
| fdc3.instrument | Apple | some-app-id |
And messaging will have posts
| type |
| intentResultRequest |

Scenario: Intent Listeners Can Return Results (Context)
Given "resultHandler" returns a context item
When I call "{api1}" with "addIntentListener" with parameters "BuyStock" and "{resultHandler}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Given('A Dummy Desktop Agent in {string}', async function (this: CustomWorld, fi
raiseIntent: notImplemented,
raiseIntentForContext: notImplemented,
addIntentListener: notImplemented,
addIntentListenerWithContext: notImplemented,
addContextListener: notImplemented,
addEventListener: notImplemented,
getUserChannels: notImplemented,
Expand Down
12 changes: 11 additions & 1 deletion packages/fdc3-schema/generated/api/BrowserTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,10 @@ export interface AddIntentListenerRequest {
* The message payload typically contains the arguments to FDC3 API functions.
*/
export interface AddIntentListenerRequestPayload {
/**
* The types of context to listen for.
*/
contextTypes?: string[];
/**
* The name of the intent to listen for.
*/
Expand Down Expand Up @@ -5171,7 +5175,13 @@ const typeMap: any = {
],
false
),
AddIntentListenerRequestPayload: o([{ json: 'intent', js: 'intent', typ: '' }], false),
AddIntentListenerRequestPayload: o(
[
{ json: 'contextTypes', js: 'contextTypes', typ: u(undefined, a('')) },
{ json: 'intent', js: 'intent', typ: '' },
],
false
),
AddIntentListenerResponse: o(
[
{ json: 'meta', js: 'meta', typ: r('AddContextListenerResponseMeta') },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,54 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://fdc3.finos.org/schemas/next/api/addIntentListenerRequest.schema.json",
"type": "object",
"title": "AddIntentListener Request",
"description": "A request to add an Intent listener for a specified intent type.",
"allOf": [
{
"$ref": "appRequest.schema.json"
},
{
"type": "object",
"properties": {
"type": {
"$ref": "#/$defs/AddIntentListenerRequestType"
},
"payload": {
"$ref": "#/$defs/AddIntentListenerRequestPayload"
},
"meta": true
},
"additionalProperties": false
}
],
"$defs": {
"AddIntentListenerRequestType": {
"title": "AddIntentListener Request Message Type",
"const": "addIntentListenerRequest"
},
"AddIntentListenerRequestPayload": {
"title": "AddIntentListener Request Payload",
"type": "object",
"properties": {
"intent": {
"title": "Intent name",
"description": "The name of the intent to listen for.",
"type": "string"
}
},
"additionalProperties": false,
"required": [
"intent"
]
}
}
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://fdc3.finos.org/schemas/next/api/addIntentListenerRequest.schema.json",
"type": "object",
"title": "AddIntentListener Request",
"description": "A request to add an Intent listener for a specified intent type.",
"allOf": [
{
"$ref": "appRequest.schema.json"
},
{
"type": "object",
"properties": {
"type": {
"$ref": "#/$defs/AddIntentListenerRequestType"
},
"payload": {
"$ref": "#/$defs/AddIntentListenerRequestPayload"
},
"meta": true
},
"additionalProperties": false
}
],
"$defs": {
"AddIntentListenerRequestType": {
"title": "AddIntentListener Request Message Type",
"const": "addIntentListenerRequest"
},
"AddIntentListenerRequestPayload": {
"title": "AddIntentListener Request Payload",
"type": "object",
"properties": {
"intent": {
"title": "Intent name",
"description": "The name of the intent to listen for.",
"type": "string"
},
"contextTypes": {
"title": "Context types",
"description": "The types of context to listen for.",
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": [
"intent"
]
}
}
}
16 changes: 16 additions & 0 deletions packages/fdc3-standard/src/api/DesktopAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,22 @@ export interface DesktopAgent {
*/
addIntentListener(intent: Intent, handler: IntentHandler): Promise<Listener>;

/**
* Adds a listener for incoming intents raised by other applications, via calls to `fdc3.raiseIntent` or `fdc3.raiseIntentForContext, but filters intents for one or more context types. See `addIntentListener` for details and restrictions for both usage and implementation.
*
* //Handle a raised intent
* const listener = fdc3.addIntentListenerWithContext('StartChat', 'fdc3.contact', context => {
* // start chat has been requested by another application
* return;
* });
*/

addIntentListenerWithContext(
intent: Intent,
contextType: string | string[],
handler: IntentHandler
): Promise<Listener>;

/**
* Adds a listener for incoming context broadcasts from the Desktop Agent (via a User channel or `fdc3.open`API call. 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.
*
Expand Down
7 changes: 7 additions & 0 deletions packages/testing/src/steps/generic.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import fs from 'fs';
import path from 'path';

export function setupGenericSteps() {
Given(
'{string} is an array of contexts including {string} and {string}',
function (field: string, valueOne: string, valueTwo: string) {
this.props[field] = [valueOne, valueTwo];
}
);

Then('the promise {string} should resolve', async function (this: PropsWorld, field: string) {
try {
const promise = handleResolve(field, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type ListenerRegistration = {
appId: string;
instanceId: string;
intentName: string;
contextTypes?: string[];
listenerUUID: string;
};

Expand Down Expand Up @@ -114,6 +115,7 @@ class PendingIntent {
if (
arg0.appId == this.appId.appId &&
arg0.intentName == this.r.intent &&
(arg0.contextTypes == undefined || arg0.contextTypes.includes(this.r.context.type)) &&
(arg0.instanceId == this.appId.instanceId || this.appId.instanceId == undefined)
) {
this.complete = true;
Expand Down Expand Up @@ -248,6 +250,7 @@ export class IntentHandler implements MessageHandler {
appId: from.appId,
instanceId: from.instanceId,
intentName: arg0.payload.intent,
contextTypes: arg0.payload.contextTypes,
listenerUUID: sc.createUUID(),
};

Expand Down Expand Up @@ -377,7 +380,11 @@ export class IntentHandler implements MessageHandler {
async raiseIntentToAnyApp(arg0: IntentRequest[], sc: ServerContext<AppRegistration>): Promise<void> {
const connectedApps = await sc.getConnectedApps();
const matchingIntents = arg0.flatMap(i => this.directory.retrieveIntents(i.context.type, i.intent, undefined));
const matchingRegistrations = arg0.flatMap(i => this.registrations.filter(r => r.intentName == i.intent));
const matchingRegistrations = arg0.flatMap(i =>
this.registrations.filter(
r => r.intentName == i.intent && (r.contextTypes == null || r.contextTypes.includes(i.context.type))
)
); // Get a list of intent listeners that match the intent and context type
const uniqueIntentNames = [
...matchingIntents.map(i => i.intentName),
...matchingRegistrations.map(r => r.intentName),
Expand Down
Loading
Loading