Skip to content

Commit 91d62e7

Browse files
committed
Implementing ContextMetadata support in fdc3-web-impl and adding getCurrentContextWithMetadata function
1 parent 449db1b commit 91d62e7

27 files changed

Lines changed: 463 additions & 135 deletions

packages/fdc3-agent-proxy/src/channels/DefaultChannel.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
22
ContextHandler,
3+
ContextWithMetadata,
4+
ContextMetadata,
35
DisplayMetadata,
46
Listener,
57
Channel,
@@ -78,6 +80,40 @@ export class DefaultChannel implements Channel {
7880
return response.payload.context ?? null;
7981
}
8082

83+
/**
84+
* Retrieves the current context along with its metadata.
85+
* Used by the proxy to deliver metadata to context listeners when replaying
86+
* context after a channel change.
87+
*/
88+
async getCurrentContextWithMetadata(contextType?: string): Promise<ContextWithMetadata | null> {
89+
const request: GetCurrentContextRequest = {
90+
meta: this.messaging.createMeta(),
91+
payload: {
92+
channelId: this.id,
93+
contextType: contextType ?? null,
94+
},
95+
type: 'getCurrentContextRequest',
96+
};
97+
const response = await this.messaging.exchange<GetCurrentContextResponse>(
98+
request,
99+
'getCurrentContextResponse',
100+
this.messageExchangeTimeout
101+
);
102+
103+
const context = response.payload.context;
104+
if (context) {
105+
const metadata: ContextMetadata = {
106+
source: response.payload.metadata?.source ?? { appId: 'unknown' },
107+
timestamp: response.payload.metadata?.timestamp ?? response.meta.timestamp,
108+
traceId: response.payload.metadata?.traceId ?? '',
109+
signature: response.payload.metadata?.signature,
110+
custom: response.payload.metadata?.custom,
111+
};
112+
return { context, metadata };
113+
}
114+
return null;
115+
}
116+
81117
async addContextListener(
82118
contextTypeOrHandler: string | null | ContextHandler,
83119
handler?: ContextHandler

packages/fdc3-agent-proxy/src/channels/DefaultChannelSupport.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,10 @@ export class DefaultChannelSupport implements ChannelSupport, Connectable {
295295

296296
async changeChannel(): Promise<void> {
297297
if (this.container.currentChannel != null) {
298-
const context = await this.container.currentChannel?.getCurrentContext(this.contextType ?? undefined);
299-
if (context) {
300-
this.handler(context);
298+
const channel = this.container.currentChannel as DefaultChannel;
299+
const result = await channel.getCurrentContextWithMetadata(this.contextType ?? undefined);
300+
if (result) {
301+
this.handler(result.context, result.metadata);
301302
}
302303
}
303304
}

packages/fdc3-agent-proxy/src/listeners/DefaultContextListener.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { ContextHandler, DesktopAgentProvidableContextMetadata } from '@finos/fdc3-standard';
1+
import { ContextHandler, ContextMetadata } from '@finos/fdc3-standard';
22
import { Messaging } from '../Messaging.js';
33
import { AbstractListener } from './AbstractListener.js';
44
import { AddContextListenerRequest, BroadcastEvent } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes.js';
55
import { RegisterableListener } from './RegisterableListener.js';
66

7-
87
export class DefaultContextListener
98
extends AbstractListener<ContextHandler, AddContextListenerRequest>
109
implements RegisterableListener
@@ -45,10 +44,12 @@ export class DefaultContextListener
4544
}
4645

4746
action(m: BroadcastEvent): void {
48-
const metadata: DesktopAgentProvidableContextMetadata = {
47+
const metadata: ContextMetadata = {
4948
source: m.payload.metadata?.source,
5049
timestamp: m.payload.metadata?.timestamp ?? m.meta.timestamp,
5150
traceId: m.payload.metadata?.traceId,
51+
signature: m.payload.metadata?.signature,
52+
custom: m.payload.metadata?.custom,
5253
};
5354
this.handler(m.payload.context, metadata);
5455
}

packages/fdc3-agent-proxy/src/listeners/DefaultIntentListener.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export class DefaultIntentListener extends AbstractListener<IntentHandler, AddIn
3737
source: m.payload.metadata?.source as AppIdentifier,
3838
timestamp: m.payload.metadata?.timestamp ?? m.meta.timestamp,
3939
traceId: m.payload.metadata?.traceId ?? v4(),
40+
signature: m.payload.metadata?.signature,
41+
custom: m.payload.metadata?.custom,
4042
});
4143

4244
this.handleIntentResult(done, m);

packages/fdc3-schema/generated/api/BrowserTypes.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,11 @@ export interface ContextMetadata {
12641264
* standardized.
12651265
*/
12661266
custom?: { [key: string]: any };
1267+
/**
1268+
* A cryptographic signature that can be used to verify the authenticity and integrity of
1269+
* the context or intent message.
1270+
*/
1271+
signature?: string;
12671272
/**
12681273
* Identifier for the app instance that sent the context and/or intent.
12691274
*/
@@ -1327,6 +1332,7 @@ export interface BroadcastRequestPayload {
13271332
*/
13281333
export interface AppProvidableContextMetadata {
13291334
custom?: { [key: string]: any };
1335+
signature?: string;
13301336
traceId?: string;
13311337
}
13321338

@@ -2732,6 +2738,13 @@ export interface GetCurrentContextResponsePayload {
27322738
* or `null` if none was available in the channel.
27332739
*/
27342740
context?: null | Context;
2741+
/**
2742+
* Metadata relating to the most recently broadcast context object, if available. This is
2743+
* not returned by the public getCurrentContext API but is used internally by the Desktop
2744+
* Agent proxy to deliver metadata to context listeners when replaying context after a
2745+
* channel change.
2746+
*/
2747+
metadata?: ContextMetadata | null;
27352748
}
27362749

27372750
/**
@@ -5305,6 +5318,7 @@ const typeMap: any = {
53055318
ContextMetadata: o(
53065319
[
53075320
{ json: 'custom', js: 'custom', typ: u(undefined, m('any')) },
5321+
{ json: 'signature', js: 'signature', typ: u(undefined, '') },
53085322
{ json: 'source', js: 'source', typ: r('AppIdentifier') },
53095323
{ json: 'timestamp', js: 'timestamp', typ: Date },
53105324
{ json: 'traceId', js: 'traceId', typ: '' },
@@ -5330,6 +5344,7 @@ const typeMap: any = {
53305344
AppProvidableContextMetadata: o(
53315345
[
53325346
{ json: 'custom', js: 'custom', typ: u(undefined, m('any')) },
5347+
{ json: 'signature', js: 'signature', typ: u(undefined, '') },
53335348
{ json: 'traceId', js: 'traceId', typ: u(undefined, '') },
53345349
],
53355350
false
@@ -5780,6 +5795,7 @@ const typeMap: any = {
57805795
[
57815796
{ json: 'error', js: 'error', typ: u(undefined, r('PurpleError')) },
57825797
{ json: 'context', js: 'context', typ: u(undefined, u(null, r('Context'))) },
5798+
{ json: 'metadata', js: 'metadata', typ: u(undefined, u(r('ContextMetadata'), null)) },
57835799
],
57845800
false
57855801
),

packages/fdc3-schema/generated/bridging/BridgingTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ export interface Context {
661661
*/
662662
export interface AppProvidableContextMetadata {
663663
custom?: { [key: string]: any };
664+
signature?: string;
664665
traceId?: string;
665666
}
666667

@@ -5126,6 +5127,7 @@ const typeMap: any = {
51265127
AppProvidableContextMetadata: o(
51275128
[
51285129
{ json: 'custom', js: 'custom', typ: u(undefined, m('any')) },
5130+
{ json: 'signature', js: 'signature', typ: u(undefined, '') },
51295131
{ json: 'traceId', js: 'traceId', typ: u(undefined, '') },
51305132
],
51315133
false

packages/fdc3-schema/schemas/api/api.schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@
288288
"title": "timestamp",
289289
"description": "The timestamp when the context or intent was created, encoded according to [ISO 8601-1:2019](https://www.iso.org/standard/70907.html) with a timezone indicator."
290290
},
291+
"signature": {
292+
"type": "string",
293+
"title": "signature",
294+
"description": "A cryptographic signature that can be used to verify the authenticity and integrity of the context or intent message."
295+
},
291296
"custom": {
292297
"type": "object",
293298
"additionalProperties": true,
@@ -308,6 +313,7 @@
308313
"title": "App Providable Context Metadata",
309314
"properties": {
310315
"traceId": {"$ref": "#/definitions/ContextMetadata/properties/traceId"},
316+
"signature": {"$ref": "#/definitions/ContextMetadata/properties/signature"},
311317
"custom": {"$ref": "#/definitions/ContextMetadata/properties/custom"}
312318
},
313319
"additionalProperties": false

packages/fdc3-schema/schemas/api/getCurrentContextResponse.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@
4545
{ "$ref": "../context/context.schema.json" },
4646
{ "type": "null" }
4747
]
48+
},
49+
"metadata": {
50+
"title": "Context Metadata",
51+
"description": "Metadata relating to the most recently broadcast context object, if available. This is not returned by the public getCurrentContext API but is used internally by the Desktop Agent proxy to deliver metadata to context listeners when replaying context after a channel change.",
52+
"oneOf": [
53+
{ "$ref": "api.schema.json#/definitions/ContextMetadata" },
54+
{ "type": "null" }
55+
]
4856
}
4957
},
5058
"required": [

packages/fdc3-standard/src/api/Channel.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { Context } from '@finos/fdc3-context';
7-
import { ContextHandler } from './Types.js';
7+
import { ContextHandler, ContextWithMetadata } from './Types.js';
88
import { DisplayMetadata } from './DisplayMetadata.js';
99
import { Listener } from './Listener.js';
1010
import { EventHandler } from './Events.js';
@@ -68,6 +68,24 @@ export interface Channel {
6868
*/
6969
getCurrentContext(contextType?: string): Promise<Context | null>;
7070

71+
/**
72+
* Returns the most recent context that was broadcast on the channel, along
73+
* with its associated metadata, or `null` if no matching context is found.
74+
*
75+
* When a `contextType` is provided, the most recent context matching the type
76+
* will be returned. If no `contextType` is provided, the most recent context
77+
* that was broadcast on the channel - regardless of type - will be returned.
78+
*
79+
* This function is similar to `getCurrentContext()` but additionally returns
80+
* the `ContextMetadata` that was associated with the context when it was
81+
* broadcast, allowing applications to access information such as the source
82+
* app, timestamp, traceId, signature and any custom metadata.
83+
*
84+
* If getting the current context fails, the promise will be rejected with an
85+
* `Error` with a `message` string from the `ChannelError` enumeration.
86+
*/
87+
getCurrentContextWithMetadata(contextType?: string): Promise<ContextWithMetadata | null>;
88+
7189
/**
7290
* Adds a listener for incoming contexts of the specified _context type_ whenever a broadcast happens on this channel.
7391
*

packages/fdc3-standard/src/api/ContextMetadata.ts

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,12 @@
66
import { AppIdentifier } from './AppIdentifier.js';
77

88
/**
9-
* Metadata relating to a context or intent and context received through the
10-
* `addContextListener` and `addIntentListener` functions.
11-
*
12-
* @experimental Introduced in FDC3 2.0 and may be refined by further changes outside the normal FDC3 versioning policy.
13-
*/
14-
export interface ContextMetadata {
15-
/** Identifier for the app instance that sent the context and/or intent.
16-
*
17-
* @experimental
18-
*/
19-
readonly source: AppIdentifier;
20-
}
21-
9+
* Metadata that may be provided by an App when calling broadcast, open or
10+
* raiseIntent functions, to be passed onto receiving apps.
11+
* */
2212
export interface AppProvidableContextMetadata {
23-
/** A unique identifier for tracing the flow of context or intent messages across applications.
13+
/** A unique identifier for tracing the flow of context or intent messages
14+
* across applications.
2415
* This is useful for debugging and monitoring message flow in complex interop scenarios.
2516
* If a traceId is provided by the app, the Desktop Agent SHOULD forward it.
2617
* If no traceId is provided by the app, the Desktop Agent SHOULD generate a new one.
@@ -31,24 +22,24 @@ export interface AppProvidableContextMetadata {
3122
* of the context or intent message. This is useful for security-sensitive applications.
3223
* If a signature is provided by an app, it MAY be verified by the Desktop Agent. */
3324
signature?: string;
25+
26+
/**
27+
* Custom metadata that can be used to provide additional information about
28+
* the context or intent. This allows for individuals to use metadata fields
29+
* that have yet to be standardized.
30+
*/
31+
custom?: Record<string, any>;
3432
}
3533

36-
export interface DesktopAgentProvidableContextMetadata {
34+
/**
35+
* Metadata relating to a context or intent and context received through the
36+
* `addContextListener` and `addIntentListener` functions.
37+
*/
38+
export interface ContextMetadata extends AppProvidableContextMetadata {
3739
/** The timestamp when the context was broadcast or the intent was raised.
3840
* This can be used for debugging, auditing, or ordering events. */
39-
timestamp?: Date;
41+
timestamp: Date;
4042

4143
/** The identifier of the app instance that originated the context or intent. */
42-
source?: AppIdentifier;
43-
44-
/** A unique identifier for tracing the flow of context or intent messages across applications.
45-
* This is useful for debugging and monitoring message flow in complex interop scenarios.
46-
* If a traceId is provided by the app, the Desktop Agent SHOULD forward it.
47-
* If no traceId is provided by the app, the Desktop Agent SHOULD generate a new one.
48-
* */
49-
traceId?: string;
50-
51-
/** A cryptographic signature that can be used to verify the authenticity and integrity
52-
* of the context or intent message. This is useful for security-sensitive applications. */
53-
signature?: string;
44+
source: AppIdentifier;
5445
}

0 commit comments

Comments
 (0)