Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context Clearing #1379

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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 @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Added utility functions `isStandardContextType(contextType: string)`, `isStandardIntent(intent: string)`,`getPossibleContextsForIntent(intent: StandardIntent)`. ([#1139](https://github.com/finos/FDC3/pull/1139))
* Added support for event listening outside of intent or context listnener. Added new function `addEventListener`, type `EventHandler`, enum `FDC3EventType` and interfaces `FDC3Event` and `FDC3ChannelChangedEvent`. ([#1207](https://github.com/finos/FDC3/pull/1207))
* Added new `CreateOrUpdateProfile` intent. ([#1359](https://github.com/finos/FDC3/pull/1359))
* Added `clearContext` function to the `Channel` API, to be able to clear specific or all context types from the channel. ([#1379](https://github.com/finos/FDC3/pull/1379))

### Changed

Expand Down
95 changes: 95 additions & 0 deletions docs/api/ref/Channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ interface Channel {
broadcast(context: Context): Promise<void>;
getCurrentContext(contextType?: string): Promise<Context|null>;
addContextListener(contextType: string | null, handler: ContextHandler): Promise<Listener>;
clearContext(contextType?: string): Promise<void>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should require an explicit null argument (contextType: string | null) to clear all types - which would match addContectListener or stick with this (matching getCurrentContext). Either works of course.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it makes sense to keep it closer to getCurrentContext, because they seem most related to me, but happy to hear any thoughts/suggestions.

Copy link
Contributor

@kriswest kriswest Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a historical reason for addContextListener having an explicit null argument, the contextType argument was originally optional so the handler argument could be either first or second (when a type was specified). This was NOT good in practice so we moved to an explicit null argument and deprecated the single argument override:
https://fdc3.finos.org/docs/2.0/api/ref/DesktopAgent#addcontextlistener-deprecated

We don't have that problem here, so I agree its fine as is (matching getCurrentContext).


//deprecated functions
/**
Expand All @@ -57,6 +58,7 @@ interface IChannel: IIntentResult
Task Broadcast(IContext context);
Task<IContext?> GetCurrentContext(string? contextType);
Task<IListener> AddContextListener<T>(string? contextType, ContextHandler<T> handler) where T : IContext;
Task ClearContext(string? contextType);
bingenito marked this conversation as resolved.
Show resolved Hide resolved
}
```

Expand Down Expand Up @@ -431,6 +433,99 @@ catch (Exception ex)
- [`broadcast`](#broadcast)
- [`addContextListener`](#addcontextlistener)

### `clearContext`

<Tabs groupId="lang">
<TabItem value="ts" label="TypeScript/JavaScript">

```ts
public clearContext(contextType?: string): Promise<void>;
```

</TabItem>
<TabItem value="dotnet" label=".NET">

```csharp
Task ClearContext(string? contextType);
```

</TabItem>
</Tabs>

Used to clear the specified context type if provided, otherwise, clear all context types present in the channel. The Desktop Agent MUST update its internal representation of the context in the channel and ensure that subsequent calls to [`getCurrentContext`](#getcurrentcontext) and any new joiners to that channel (through [`joinUserChannel`](DesktopAgent#joinUserChannel) or [`addContextListener`](DesktopAgent#addContextListener)) will not receive anything for either specified context type or the most recent context until new context has been broadcast to the channel.
Desktop Agents MUST also immediately broadcast the [`fdc3.nothing`](../../context/ref/Nothing.md/#nothing) type, which applications may listen to to be notified of the cleared context. If a `contextType` parameter was provided, then the `contextType` field will be set to that type, otherwise, it is omitted.


**Examples:**

Without specifying a context type:

<Tabs groupId="lang">
<TabItem value="ts" label="TypeScript/JavaScript">

```ts
try {
const context = await channel.clearContext();
} catch (err: ChannelError) {
// handle error
}
```

</TabItem>
<TabItem value="dotnet" label=".NET">

```csharp
try
{
var context = await channel.ClearContext();
}
catch (Exception ex)
{
// handle error
}
```

</TabItem>
</Tabs>

Specifying a context type:

<Tabs groupId="lang">
<TabItem value="ts" label="TypeScript/JavaScript">

```ts
try {
const contact = await channel.clearContext('fdc3.contact');
} catch (err: ChannelError) {
// handler error
}
```

</TabItem>
<TabItem value="dotnet" label=".NET">

```csharp
try
{
var context = await channel.ClearContext("fdc3.contact");
}
catch (Exception ex)
{
// handle error
}
```

</TabItem>
</Tabs>


**See also:**

- [`getCurrentContext`](#getcurrentcontext)
- [`addContextListener`](DesktopAgent#addContextListener)
- [`joinUserChannel`](DesktopAgent#joinUserChannel)
- [`fdc3.nothing`](../../context/ref/Nothing.md/#nothing)

## Deprecated Functions

### `addContextListener` (deprecated)
Expand Down
7 changes: 5 additions & 2 deletions docs/api/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ When raising an intent a specific context is provided as input. The type of the

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).
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). This type is also used when the context is cleared for the channel. If the optional context type is provided when performing [`clearContext`](ref/Channel.md#clearcontext), that type will be recorded in the field `subType` of [`fdc3.nothing`](../context/ref/Nothing) context type.

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.

Expand Down Expand Up @@ -595,7 +595,7 @@ Apps can join *User channels*. An app can only be joined to one User channel at

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.
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, unless the context was cleared through [`clearContext`](ref/Channel.md#clearcontext).

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.

Expand Down Expand Up @@ -840,6 +840,9 @@ The [Context specification](../context/spec#assumptions) recommends that complex

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.

### Context clearning on channels
Channel interface provides the ability to [`clearContext`](ref/Channel.md#clearcontext) on the channel, either for the specific context type, if provided, or for all contexts on that channel. Applications may listen for the `fdc3.nothing` type, or all context types, to be notified when the context is cleared. If a specific type was cleared, the `contextType` field of the [`fdc3.nothing`](../context/ref/Nothing) context type will be set with that type. Once cleared, any apps that join the channel, add new context listeners or call [`getCurrentContext`](ref/Channel.md#getcurrentcontext) will not return anything to the caller (other than the `fdc3.nothing` type indicating that context was cleared) until new context is broadcast to the channel.

### 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.
7 changes: 6 additions & 1 deletion schemas/context/nothing.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
"properties": {
"type": {
"const": "fdc3.nothing"
},
"contextType": {
"type": "Context Type",
"description": "A referenced context type name. Used to indicate that a specific context type was cleared, if omitted, all contexts were cleared"
}
}
},
{ "$ref": "context.schema.json#/definitions/BaseContext" }
],
"examples": [
{
"type": "fdc3.nothing"
"type": "fdc3.nothing",
"contextType": "fdc3.timeRange"
}
]
}
9 changes: 9 additions & 0 deletions src/api/Channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ export interface Channel {
*/
addContextListener(contextType: string | null, handler: ContextHandler): Promise<Listener>;

/**
* Clears context from the channel, and broadcasts an `fdc3.nothing` context to notify existing listeners that the context was cleared. Listeners added to the channel and calls to [`getCurrentContext`](#getcurrentcontext) will not receive any existing context until new context is broadcast to the channel.
*
* If a `contextType` is provided, only contexts of that type will be cleared and the `contextType` of the `fdc3.nothing` context will be set to that type name.
*
* If no `contextType` is provided, all contexts will be cleared and the `contextType` of the `fdc3.nothing` context will be omitted.
*/
clearContext(contextType?: string): Promise<void>;

/**
* @deprecated use `addContextListener(null, handler)` instead of `addContextListener(handler)`.
*/
Expand Down
3 changes: 3 additions & 0 deletions toolbox/fdc3-conformance/App-Channel-Tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@
- `ACContextHistoryTyped`: Perform above test.
- `ACContextHistoryMultiple`: **B** Broadcasts multiple history items of both types. Ensure that only the last version of each type is received by **A**.
- `ACContextHistoryLast`: In step 5. **A** retrieves the _untyped_ current context of the channel via `const currentContext = await testChannel.getCurrentContext()`. Ensure that A receives only the very last broadcast context item _of any type_.
- `ACClearHistorySpecificContext`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.instrument')`. Ensure that in step 5 nothing is received for `const instrument = await testChannel.getCurrentContext('fdc3.instrument')` and context is received successfully for `const instrument = await testChannel.getCurrentContext('fdc3.contact')`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `ACClearHistorySpecificContext`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.instrument')`. Ensure that in step 5 nothing is received for `const instrument = await testChannel.getCurrentContext('fdc3.instrument')` and context is received successfully for `const instrument = await testChannel.getCurrentContext('fdc3.contact')`
- `ACClearHistorySpecificContext`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.instrument')`. Ensure that in step 5 nothing is received for `const instrument = await testChannel.getCurrentContext('fdc3.instrument')` and context is received successfully for `const contact = await testChannel.getCurrentContext('fdc3.contact')`

- `ACClearHistoryAllContexts`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.nothing')`. Ensure that in step 5 nothing is received for `const instrument = await testChannel.getCurrentContext('fdc3.instrument')` and for `const instrument = await testChannel.getCurrentContext('fdc3.contact')`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `ACClearHistoryAllContexts`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.nothing')`. Ensure that in step 5 nothing is received for `const instrument = await testChannel.getCurrentContext('fdc3.instrument')` and for `const instrument = await testChannel.getCurrentContext('fdc3.contact')`
- `ACClearHistoryAllContexts`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.nothing')`. Ensure that in step 5 nothing is received for `const instrument = await testChannel.getCurrentContext('fdc3.instrument')` and for `const contact = await testChannel.getCurrentContext('fdc3.contact')`

- `ACClearHistoryAllContextsSubscribedToNothing`: Perform the above test, except after step 3 clear context with `await testChannel.clearContext('fdc3.nothing')` and in step 4 call `await testChannel.addContextListener("fdc3.nothing", handler)` instead. Ensure that after clearing context, the handler is called.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few thoughts on this case:

  • I think you want to clear fdc3.instrument or all types (null) rather than fdc3.nothing
  • As written, the nothing handler is added after calling clear, in that case it won't receive anything on an app channel. The handler needs to be in place before the clear.
  • I think this needs to be 2 cases, one for clearing all types and another for a specific type that confirms the other is not affected and that the fdc3.nothing quotes the correct type.
  • Finally, I think we need at least one case that checks the behavior of user channele- you'll work with the channel as an app channel to clear it (either retrrieving it by name, via getUserChannels, or getCUrrentChannel) but we should also confirm that it gets the fdc3.nothing broadcast and that it doesn't retain the cleared context.