diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/AddContextListenerRequest.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/AddContextListenerRequest.cs
new file mode 100644
index 000000000..909fc9b9c
--- /dev/null
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/AddContextListenerRequest.cs
@@ -0,0 +1,40 @@
+/*
+* Morgan Stanley makes this available to you under the Apache License,
+* Version 2.0 (the "License"). You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0.
+*
+* See the NOTICE file distributed with this work for additional information
+* regarding copyright ownership. Unless required by applicable law or agreed
+* to in writing, software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+* or implied. See the License for the specific language governing permissions
+* and limitations under the License.
+*/
+
+using Finos.Fdc3;
+
+namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
+
+internal sealed class AddContextListenerRequest
+{
+ ///
+ /// Instance id of the app that sent the request.
+ ///
+ public string Fdc3InstanceId { get; set; }
+
+ ///
+ /// Type of the context that the listener should listen on.
+ ///
+ public string? ContextType { get; set; }
+
+ ///
+ /// The id of the channel, that the current listener is listening on.
+ ///
+ public string ChannelId { get; set; }
+
+ ///
+ /// The type of the channel that the current listener listens on.
+ ///
+ public ChannelType ChannelType { get; set; }
+}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/AddContextListenerResponse.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/AddContextListenerResponse.cs
new file mode 100644
index 000000000..8138008d9
--- /dev/null
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/AddContextListenerResponse.cs
@@ -0,0 +1,36 @@
+/*
+* Morgan Stanley makes this available to you under the Apache License,
+* Version 2.0 (the "License"). You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0.
+*
+* See the NOTICE file distributed with this work for additional information
+* regarding copyright ownership. Unless required by applicable law or agreed
+* to in writing, software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+* or implied. See the License for the specific language governing permissions
+* and limitations under the License.
+*/
+
+namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
+
+internal sealed class AddContextListenerResponse
+{
+ ///
+ /// The generated id of the context listener
+ ///
+ public string? Id { get; set; }
+
+ ///
+ /// Indicates that exception was thrown during the execution.
+ ///
+ public string? Error { get; set; }
+
+ ///
+ /// Indicates if the execution was successful.
+ ///
+ public bool Success { get; set; }
+
+ public static AddContextListenerResponse Failure(string error) => new() { Error = error, Success = false };
+ public static AddContextListenerResponse Added(string id) => new() { Id = id, Success = true };
+}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/RemoveContextListenerRequest.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/RemoveContextListenerRequest.cs
new file mode 100644
index 000000000..6a8222377
--- /dev/null
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/RemoveContextListenerRequest.cs
@@ -0,0 +1,34 @@
+/*
+* Morgan Stanley makes this available to you under the Apache License,
+* Version 2.0 (the "License"). You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0.
+*
+* See the NOTICE file distributed with this work for additional information
+* regarding copyright ownership. Unless required by applicable law or agreed
+* to in writing, software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+* or implied. See the License for the specific language governing permissions
+* and limitations under the License.
+*/
+
+
+namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
+
+internal sealed class RemoveContextListenerRequest
+{
+ ///
+ /// Id of the instance that sent the request.
+ ///
+ public string Fdc3InstanceId { get; set; }
+
+ ///
+ /// Id of the context listener.
+ ///
+ public string ListenerId { get; set; }
+
+ ///
+ /// Indicates the type of the context for the subscription.
+ ///
+ public string? ContextType { get; set; }
+}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/RemoveContextListenerResponse.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/RemoveContextListenerResponse.cs
new file mode 100644
index 000000000..c8d6ac6a7
--- /dev/null
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/RemoveContextListenerResponse.cs
@@ -0,0 +1,31 @@
+/*
+* Morgan Stanley makes this available to you under the Apache License,
+* Version 2.0 (the "License"). You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0.
+*
+* See the NOTICE file distributed with this work for additional information
+* regarding copyright ownership. Unless required by applicable law or agreed
+* to in writing, software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+* or implied. See the License for the specific language governing permissions
+* and limitations under the License.
+*/
+
+namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
+
+internal sealed class RemoveContextListenerResponse
+{
+ ///
+ /// Indicates that error was thrown during the execution of the request.
+ ///
+ public string? Error { get; set; }
+
+ ///
+ /// Indicates the state of the request.
+ ///
+ public bool Success { get; set; }
+
+ public static RemoveContextListenerResponse Failure(string error) => new() {Error = error, Success = false};
+ public static RemoveContextListenerResponse Executed() => new() { Success = true};
+}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/SubscribeState.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/SubscribeState.cs
index 1b7dbbb23..988af353b 100644
--- a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/SubscribeState.cs
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Contracts/SubscribeState.cs
@@ -18,7 +18,7 @@
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
///
-/// Indicates the state of the IntentListener which was sent to the DesktopAgent backend.
+/// Indicates the state of the Listener which was sent to the DesktopAgent backend.
///
internal enum SubscribeState
{
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Exceptions/Fdc3DesktopAgentErrors.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Exceptions/Fdc3DesktopAgentErrors.cs
index 843f35d1e..c1b51d1f9 100644
--- a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Exceptions/Fdc3DesktopAgentErrors.cs
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Exceptions/Fdc3DesktopAgentErrors.cs
@@ -40,4 +40,9 @@ public static class Fdc3DesktopAgentErrors
/// Indicates that no user channel set was configured.
///
public const string NoUserChannelSetFound = $"{nameof(NoUserChannelSetFound)}";
+
+ ///
+ /// Indicates that the listener was not found for execution.
+ ///
+ public const string ListenerNotFound = $"{nameof(ListenerNotFound)}";
}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3DesktopAgent.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3DesktopAgent.cs
index 0b75fcda3..f5f37ad85 100644
--- a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3DesktopAgent.cs
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3DesktopAgent.cs
@@ -38,6 +38,9 @@
using ImplementationMetadata = MorganStanley.ComposeUI.Fdc3.DesktopAgent.Protocol.ImplementationMetadata;
using IntentMetadata = Finos.Fdc3.AppDirectory.IntentMetadata;
using Screenshot = MorganStanley.ComposeUI.Fdc3.DesktopAgent.Protocol.Screenshot;
+using AppChannel = MorganStanley.ComposeUI.Fdc3.DesktopAgent.Channels.AppChannel;
+using ImplementationMetadata = MorganStanley.ComposeUI.Fdc3.DesktopAgent.Protocol.ImplementationMetadata;
+using Constants = MorganStanley.ComposeUI.Fdc3.DesktopAgent.Infrastructure.Internal.Constants;
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent;
@@ -56,7 +59,9 @@ internal class Fdc3DesktopAgent : IFdc3DesktopAgentBridge
private readonly ConcurrentDictionary _runningModules = new();
private readonly ConcurrentDictionary _raisedIntentResolutions = new();
private readonly ConcurrentDictionary> _pendingStartRequests = new();
+ private readonly ConcurrentDictionary> _contextListeners = new();
private IAsyncDisposable? _subscription;
+ private readonly object _contextListenerLock = new();
public Fdc3DesktopAgent(
IAppDirectory appDirectory,
@@ -102,7 +107,7 @@ public Fdc3DesktopAgent(
{
if (_logger.IsEnabled(LogLevel.Warning))
{
- _logger.LogWarning(exception, $"{channelId} is already registed as service endpoint.");
+ _logger.LogWarning(exception, $"{channelId} is already registered as service endpoint.");
}
return userChannel;
@@ -133,7 +138,7 @@ public async ValueTask AddPrivateChannel(Func addPrivate
{
if (_logger.IsEnabled(LogLevel.Warning))
{
- _logger.LogWarning(exception, $"{privateChannelId} is already registed as service endpoint.");
+ _logger.LogWarning(exception, $"{privateChannelId} is already registered as service endpoint.");
}
}
catch (Exception exception)
@@ -174,7 +179,7 @@ public async ValueTask AddAppChannel(Func GetAppMetadata(GetAppMetadataRequ
}
}
+ public ValueTask AddContextListener(AddContextListenerRequest? request)
+ {
+ if (request == null)
+ {
+ return ValueTask.FromResult(AddContextListenerResponse.Failure(Fdc3DesktopAgentErrors.PayloadNull));
+ }
+
+ if (!Guid.TryParse(request.Fdc3InstanceId, out var originFdc3InstanceId) || !_runningModules.TryGetValue(originFdc3InstanceId, out _))
+ {
+ return ValueTask.FromResult(AddContextListenerResponse.Failure(Fdc3DesktopAgentErrors.MissingId));
+ }
+
+ lock (_contextListenerLock)
+ {
+ var contextListener = new ContextListener(
+ request.ContextType,
+ request.ChannelId,
+ request.ChannelType);
+
+ _contextListeners.AddOrUpdate(
+ originFdc3InstanceId,
+ _ => new List { contextListener },
+ (_, contextListeners) =>
+ {
+ contextListeners.Add(contextListener);
+ return contextListeners;
+ });
+
+ return ValueTask.FromResult(AddContextListenerResponse.Added(contextListener.Id.ToString()));
+ }
+ }
+
+ public ValueTask RemoveContextListener(RemoveContextListenerRequest? request)
+ {
+ if (request == null)
+ {
+ return ValueTask.FromResult(RemoveContextListenerResponse.Failure(Fdc3DesktopAgentErrors.PayloadNull));
+ }
+
+ lock (_contextListenerLock)
+ {
+ if (!Guid.TryParse(request.Fdc3InstanceId, out var originFdc3InstanceId)
+ || !_runningModules.TryGetValue(originFdc3InstanceId, out _)
+ || !_contextListeners.TryGetValue(originFdc3InstanceId, out var listeners)
+ || request.ListenerId == null
+ || !Guid.TryParse(request.ListenerId, out var listenerId))
+ {
+ return ValueTask.FromResult(RemoveContextListenerResponse.Failure(Fdc3DesktopAgentErrors.MissingId));
+ }
+
+ var listener = listeners.FirstOrDefault(x => x.Id == listenerId && x.ContextType == request.ContextType);
+ if (listener == null)
+ {
+ return ValueTask.FromResult(RemoveContextListenerResponse.Failure(Fdc3DesktopAgentErrors.ListenerNotFound));
+ }
+
+ listeners.Remove(listener);
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ _logger.LogDebug("ContextListener has been successfully unsubscribed.");
+ }
+
+ return ValueTask.FromResult(RemoveContextListenerResponse.Executed());
+ }
+ }
+
public async ValueTask> RaiseIntent(RaiseIntentRequest? request)
{
if (request == null)
@@ -1218,7 +1296,12 @@ private Task RemoveModuleAsync(IModuleInstance instance)
return Task.CompletedTask;
}
- if (!_runningModules.TryRemove(new(fdc3InstanceId!), out _)) //At this point the fdc3InstanceId shouldn't be null
+ if (!Guid.TryParse(fdc3InstanceId, out var id))
+ {
+ return Task.CompletedTask;
+ }
+
+ if (!_runningModules.TryRemove(id, out _)) //At this point the fdc3InstanceId shouldn't be null
{
_logger.LogError($"Could not remove the closed window with instanceId: {fdc3InstanceId}.");
}
@@ -1228,6 +1311,19 @@ private Task RemoveModuleAsync(IModuleInstance instance)
taskCompletionSource.SetException(ThrowHelper.TargetInstanceUnavailable());
}
+ lock (_contextListenerLock)
+ {
+ if (!_contextListeners.TryRemove(id, out _))
+ {
+ _logger.LogError($"Could not remove the registered context listeners of id: {fdc3InstanceId}.");
+ }
+ }
+
+ if (!_raisedIntentResolutions.TryRemove(id, out _))
+ {
+ _logger.LogError($"Could not remove the stored intent resolutions of id: {fdc3InstanceId} which raised the intents.");
+ }
+
return Task.CompletedTask;
}
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3Topic.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3Topic.cs
index f87efe31a..9d732779e 100644
--- a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3Topic.cs
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3Topic.cs
@@ -12,9 +12,7 @@
* and limitations under the License.
*/
-using System.Threading.Tasks.Dataflow;
using Finos.Fdc3;
-using MorganStanley.ComposeUI.Messaging.Protocol.Messages;
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent;
@@ -36,6 +34,8 @@ internal static class Fdc3Topic
internal static string GetInfo => TopicRoot + "getInfo";
internal static string FindInstances => TopicRoot + "findInstances";
internal static string GetAppMetadata => TopicRoot + "getAppMetadata";
+ internal static string AddContextListener => TopicRoot + "addContextListener";
+ internal static string RemoveContextListener => TopicRoot + "removeContextListener";
//IntentListeners will be listening at this endpoint
internal static string RaiseIntentResolution(string intent, string instanceId)
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/ContextListener.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/ContextListener.cs
new file mode 100644
index 000000000..375750c23
--- /dev/null
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/ContextListener.cs
@@ -0,0 +1,36 @@
+/*
+* Morgan Stanley makes this available to you under the Apache License,
+* Version 2.0 (the "License"). You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0.
+*
+* See the NOTICE file distributed with this work for additional information
+* regarding copyright ownership. Unless required by applicable law or agreed
+* to in writing, software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+* or implied. See the License for the specific language governing permissions
+* and limitations under the License.
+*/
+
+using Finos.Fdc3;
+
+namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Infrastructure.Internal;
+
+internal class ContextListener
+{
+ private readonly string? _contextType;
+ private readonly Guid _instanceId;
+ private string? _channelId;
+ private ChannelType? _channelType;
+
+ public Guid Id => _instanceId;
+ public string? ContextType => _contextType;
+
+ public ContextListener(string? contextType, string channelId, ChannelType channelType)
+ {
+ _contextType = contextType;
+ _channelId = channelId;
+ _channelType = channelType;
+ _instanceId = Guid.NewGuid();
+ }
+}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.cs
index 6a733e71e..db6d54593 100644
--- a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.cs
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.cs
@@ -196,6 +196,16 @@ internal async ValueTask HandleGetAppMetadata(GetAppMeta
return await _desktopAgent.GetAppMetadata(request);
}
+ internal async ValueTask HandleAddContextListener(AddContextListenerRequest? request, MessageContext? context)
+ {
+ return await _desktopAgent.AddContextListener(request);
+ }
+
+ internal async ValueTask HandleRemoveContextListener(RemoveContextListenerRequest? request, MessageContext? context)
+ {
+ return await _desktopAgent.RemoveContextListener(request);
+ }
+
private async ValueTask SafeWaitAsync(IEnumerable tasks)
{
foreach (var task in tasks)
@@ -238,6 +248,8 @@ await _messageRouter.RegisterServiceAsync(topic,
await RegisterHandler(Fdc3Topic.GetInfo, HandleGetInfo);
await RegisterHandler(Fdc3Topic.FindInstances, HandleFindInstances);
await RegisterHandler(Fdc3Topic.GetAppMetadata, HandleGetAppMetadata);
+ await RegisterHandler(Fdc3Topic.AddContextListener, HandleAddContextListener);
+ await RegisterHandler(Fdc3Topic.RemoveContextListener, HandleRemoveContextListener);
await _desktopAgent.StartAsync(cancellationToken);
@@ -265,6 +277,8 @@ public async Task StopAsync(CancellationToken cancellationToken)
_messageRouter.UnregisterServiceAsync(Fdc3Topic.GetInfo, cancellationToken),
_messageRouter.UnregisterServiceAsync(Fdc3Topic.FindInstances, cancellationToken),
_messageRouter.UnregisterServiceAsync(Fdc3Topic.GetAppMetadata, cancellationToken),
+ _messageRouter.UnregisterServiceAsync(Fdc3Topic.AddContextListener, cancellationToken),
+ _messageRouter.UnregisterServiceAsync(Fdc3Topic.RemoveContextListener, cancellationToken),
};
await SafeWaitAsync(unregisteringTasks);
diff --git a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/IFdc3DesktopAgentBridge.cs b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/IFdc3DesktopAgentBridge.cs
index 63f5816ba..5e0818e22 100644
--- a/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/IFdc3DesktopAgentBridge.cs
+++ b/src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/IFdc3DesktopAgentBridge.cs
@@ -142,4 +142,18 @@ internal interface IFdc3DesktopAgentBridge
///
///
public ValueTask GetAppMetadata(GetAppMetadataRequest? request);
+
+ ///
+ /// Handles the AddContextListener call in the bridge. It enables tracking the added contextListeners using the `fdc3.addContextListener`.
+ ///
+ ///
+ ///
+ public ValueTask AddContextListener(AddContextListenerRequest? request);
+
+ ///
+ /// Handles the ContextListener action (join/unsubscribe to a channel) call in the bridge.
+ ///
+ ///
+ ///
+ public ValueTask RemoveContextListener(RemoveContextListenerRequest? request);
}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/EndToEndTests.cs b/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/EndToEndTests.cs
index 952b98849..6d52e86b0 100644
--- a/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/EndToEndTests.cs
+++ b/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/EndToEndTests.cs
@@ -901,7 +901,6 @@ public async Task AddIntentListenerUnsubscribes()
intentListenerResponse!.Error.Should().BeNull();
}
-
[Fact]
public async Task AddAppChannelReturnsSuccessfully()
{
@@ -1082,7 +1081,7 @@ public async Task JoinUserChannelReturnsNoChannelFoundError()
}
[Fact]
- public async Task JoinUserChannel_succeeds()
+ public async Task JoinUserChannelSucceeds()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1248,7 +1247,7 @@ public async Task GetInfoSuccessfullyReturns()
}
[Fact]
- public async Task FindInstances_returns_PayloadNull_error_as_no_request()
+ public async Task FindInstancesReturnsPayloadNullErrorAsNoRequest()
{
FindInstancesRequest? request = null;
@@ -1264,7 +1263,7 @@ public async Task FindInstances_returns_PayloadNull_error_as_no_request()
}
[Fact]
- public async Task FindInstances_returns_MissingId_as_invalid_id()
+ public async Task FindInstancesReturnsMissingIdAsInvalidId()
{
var request = new FindInstancesRequest
{
@@ -1287,7 +1286,7 @@ public async Task FindInstances_returns_MissingId_as_invalid_id()
}
[Fact]
- public async Task FindInstances_returns_MissingId_error_as_no_instance_found_which_is_contained_by_the_container()
+ public async Task FindInstancesReturnsMissingIdErrorAsNoInstanceFound()
{
var request = new FindInstancesRequest
{
@@ -1310,7 +1309,7 @@ public async Task FindInstances_returns_MissingId_error_as_no_instance_found_whi
}
[Fact]
- public async Task FindInstances_returns_NoAppsFound_error_as_no_appId_found()
+ public async Task FindInstancesReturnsNoAppsFound()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1337,7 +1336,7 @@ public async Task FindInstances_returns_NoAppsFound_error_as_no_appId_found()
}
[Fact]
- public async Task FindInstances_succeeds_with_one_app()
+ public async Task FindInstancesSucceedsWithOneApp()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1365,7 +1364,7 @@ public async Task FindInstances_succeeds_with_one_app()
}
[Fact]
- public async Task FindInstances_succeeds_with_empty_array()
+ public async Task FindInstancesSucceedsWithEmptyArray()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1393,7 +1392,7 @@ public async Task FindInstances_succeeds_with_empty_array()
}
[Fact]
- public async Task GetAppMetadata_returns_PayLoadNull_error_as_request_null()
+ public async Task GetAppMetadataReturnsPayLoadNull()
{
GetAppMetadataRequest? request = null;
@@ -1410,7 +1409,7 @@ public async Task GetAppMetadata_returns_PayLoadNull_error_as_request_null()
}
[Fact]
- public async Task GetAppMetadata_returns_MissingId_error_as_initiator_id_not_found()
+ public async Task GetAppMetadataReturnsMissingId()
{
var request = new GetAppMetadataRequest
{
@@ -1434,7 +1433,7 @@ public async Task GetAppMetadata_returns_MissingId_error_as_initiator_id_not_fou
}
[Fact]
- public async Task GetAppMetadata_returns_MissingId_error_as_the_searched_instanceId_not_valid()
+ public async Task GetAppMetadataReturnsMissingIdErrorAsThSearchedInstanceIdIsNotValid()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1463,7 +1462,7 @@ public async Task GetAppMetadata_returns_MissingId_error_as_the_searched_instanc
}
[Fact]
- public async Task GetAppMetadata_returns_TargetInstanceUnavailable_error_as_the_searched_instanceId_not_found()
+ public async Task GetAppMetadataReturnsTargetInstanceUnavailableErrorAsTheSearchedInstanceIdNotFound()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1492,7 +1491,7 @@ public async Task GetAppMetadata_returns_TargetInstanceUnavailable_error_as_the_
}
[Fact]
- public async Task GetAppMetadata_returns_AppMetadata_based_on_instanceId()
+ public async Task GetAppMetadataReturnsAppMetadataBasedOnInstanceId()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1527,7 +1526,7 @@ public async Task GetAppMetadata_returns_AppMetadata_based_on_instanceId()
}
[Fact]
- public async Task GetAppMetadata_returns_TargetAppUnavailable_error_as_the_searched_appId_not_found()
+ public async Task GetAppMetadataReturnsTargetAppUnavailableErrorAsTheSearchedAppIdNotFound()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1555,7 +1554,7 @@ public async Task GetAppMetadata_returns_TargetAppUnavailable_error_as_the_searc
}
[Fact]
- public async Task GetAppMetadata_returns_AppMetadata_based_on_appId()
+ public async Task GetAppMetadataReturnsAppMetadataBasedOnAppId()
{
//TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
@@ -1586,6 +1585,180 @@ public async Task GetAppMetadata_returns_AppMetadata_based_on_appId()
});
}
+ [Fact]
+ public async Task AddContextListenerReturnsPayloadNull()
+ {
+ AddContextListenerRequest? request = null;
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.AddContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.PayloadNull);
+ }
+
+ [Fact]
+ public async Task AddContextListenerReturnsMissingId()
+ {
+ var request = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = "dummyId",
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User
+ };
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.AddContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.MissingId);
+ }
+
+ [Fact]
+ public async Task AddContextListenerSuccessfullyRegistersContextListener()
+ {
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var request = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User
+ };
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.AddContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+
+ response.Should().NotBeNull();
+ response!.Success.Should().BeTrue();
+ }
+
+ [Fact]
+ public async Task RemoveContextListenerReturnsPayloadNullError()
+ {
+ RemoveContextListenerRequest? request = null;
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.RemoveContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.PayloadNull);
+ }
+
+ [Fact]
+ public async Task RemoveContextListenerReturnsMissingIdError()
+ {
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = "dummyId",
+ ListenerId = Guid.NewGuid().ToString(),
+ };
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.RemoveContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.MissingId);
+ }
+
+ [Fact]
+ public async Task RemoveContextListenerReturnsListenerNotFoundError()
+ {
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var addContextListenerRequest = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User,
+ ContextType = "fdc3.instrument"
+ };
+
+ var addContextListenerResult = await _messageRouter.InvokeAsync(
+ Fdc3Topic.AddContextListener,
+ MessageBuffer.Factory.CreateJson(addContextListenerRequest, _options));
+
+ var addContextListenerResponse = addContextListenerResult!.ReadJson(_options);
+
+ addContextListenerResponse.Should().NotBeNull();
+ addContextListenerResponse!.Success.Should().BeTrue();
+
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = originFdc3InstanceId,
+ ListenerId = addContextListenerResponse.Id!,
+ };
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.RemoveContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.ListenerNotFound);
+ }
+
+ [Fact]
+ public async Task RemoveContextListenerSuccessfullyRemovesContextListener()
+ {
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _moduleLoader.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var addContextListenerRequest = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User,
+ ContextType = null
+ };
+
+ var addContextListenerResult = await _messageRouter.InvokeAsync(
+ Fdc3Topic.AddContextListener,
+ MessageBuffer.Factory.CreateJson(addContextListenerRequest, _options));
+
+ var addContextListenerResponse = addContextListenerResult!.ReadJson(_options);
+ addContextListenerResponse.Should().NotBeNull();
+ addContextListenerResponse!.Success.Should().BeTrue();
+
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = originFdc3InstanceId,
+ ListenerId = addContextListenerResponse.Id!,
+ };
+
+ var result = await _messageRouter.InvokeAsync(
+ Fdc3Topic.RemoveContextListener,
+ MessageBuffer.Factory.CreateJson(request, _options));
+
+ var response = result!.ReadJson(_options);
+
+ response.Should().NotBeNull();
+ response!.Success.Should().BeTrue();
+ }
+
private MessageBuffer GetContext()
{
return MessageBuffer.Factory.CreateJson(
diff --git a/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Fdc3DesktopAgentTests.cs b/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Fdc3DesktopAgentTests.cs
index bb742c34c..5b00e69eb 100644
--- a/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Fdc3DesktopAgentTests.cs
+++ b/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Fdc3DesktopAgentTests.cs
@@ -1209,4 +1209,148 @@ public async Task GetAppMetadata_returns_AppMetadata_based_on_appId()
Name = "app1"
});
}
+
+ [Fact]
+ public async Task AddContextListener_returns_payload_null_error()
+ {
+ AddContextListenerRequest? request = null;
+
+ var response = await _fdc3.AddContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.PayloadNull);
+ }
+
+ [Fact]
+ public async Task AddContextListener_returns_missing_id_error()
+ {
+ var request = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = "dummyId",
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User
+ };
+
+ var response = await _fdc3.AddContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.MissingId);
+ }
+
+ [Fact]
+ public async Task AddContextListener_successfully_registers_context_listener()
+ {
+ await _fdc3.StartAsync(CancellationToken.None);
+
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _mockModuleLoader.Object.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var request = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User
+ };
+
+ var response = await _fdc3.AddContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Success.Should().BeTrue();
+ }
+
+ [Fact]
+ public async Task RemoveContextListener_returns_payload_null_error()
+ {
+ RemoveContextListenerRequest? request = null;
+
+ var response = await _fdc3.RemoveContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.PayloadNull);
+ }
+
+ [Fact]
+ public async Task RemoveContextListener_returns_missing_id_error()
+ {
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = "dummyId",
+ ListenerId = Guid.NewGuid().ToString(),
+ };
+
+ var response = await _fdc3.RemoveContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.MissingId);
+ }
+
+ [Fact]
+ public async Task RemoveContextListener_returns_listener_not_found_error()
+ {
+ await _fdc3.StartAsync(CancellationToken.None);
+
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _mockModuleLoader.Object.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var addContextListenerRequest = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User,
+ ContextType = "fdc3.instrument"
+ };
+
+ var addContextListenerResponse = await _fdc3.AddContextListener(addContextListenerRequest);
+ addContextListenerResponse.Should().NotBeNull();
+ addContextListenerResponse!.Success.Should().BeTrue();
+
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = originFdc3InstanceId,
+ ListenerId = addContextListenerResponse.Id!,
+ };
+
+ var response = await _fdc3.RemoveContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.ListenerNotFound);
+ }
+
+ [Fact]
+ public async Task RemoveContextListener_successfully_removes_context_listener()
+ {
+ await _fdc3.StartAsync(CancellationToken.None);
+
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _mockModuleLoader.Object.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var addContextListenerRequest = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User,
+ ContextType = null
+ };
+
+ var addContextListenerResponse = await _fdc3.AddContextListener(addContextListenerRequest);
+ addContextListenerResponse.Should().NotBeNull();
+ addContextListenerResponse!.Success.Should().BeTrue();
+
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = originFdc3InstanceId,
+ ListenerId = addContextListenerResponse.Id!,
+ };
+
+ var response = await _fdc3.RemoveContextListener(request);
+
+ response.Should().NotBeNull();
+ response!.Success.Should().BeTrue();
+ }
}
\ No newline at end of file
diff --git a/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs b/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs
index 752bed751..a9e6a41be 100644
--- a/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs
+++ b/src/fdc3/dotnet/DesktopAgent/test/MorganStanley.ComposeUI.DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs
@@ -1540,6 +1540,150 @@ public async Task HandleGetAppMetadata_returns_AppMetadata_based_on_appId()
});
}
+ [Fact]
+ public async Task HandleAddContextListener_returns_payload_null_error()
+ {
+ AddContextListenerRequest? request = null;
+
+ var response = await _fdc3.HandleAddContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.PayloadNull);
+ }
+
+ [Fact]
+ public async Task HandleAddContextListener_returns_missing_id_error()
+ {
+ var request = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = "dummyId",
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User
+ };
+
+ var response = await _fdc3.HandleAddContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.MissingId);
+ }
+
+ [Fact]
+ public async Task HandleAddContextListener_successfully_registers_context_listener()
+ {
+ await _fdc3.StartAsync(CancellationToken.None);
+
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _mockModuleLoader.Object.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var request = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User
+ };
+
+ var response = await _fdc3.HandleAddContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Success.Should().BeTrue();
+ }
+
+ [Fact]
+ public async Task HandleRemoveContextListener_returns_payload_null_error()
+ {
+ RemoveContextListenerRequest? request = null;
+
+ var response = await _fdc3.HandleRemoveContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.PayloadNull);
+ }
+
+ [Fact]
+ public async Task HandleRemoveContextListener_returns_missing_id_error()
+ {
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = "dummyId",
+ ListenerId = Guid.NewGuid().ToString(),
+ };
+
+ var response = await _fdc3.HandleRemoveContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.MissingId);
+ }
+
+ [Fact]
+ public async Task HandleRemoveContextListener_returns_listener_not_found_error()
+ {
+ await _fdc3.StartAsync(CancellationToken.None);
+
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _mockModuleLoader.Object.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var addContextListenerRequest = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User,
+ ContextType = "fdc3.instrument"
+ };
+
+ var addContextListenerResponse = await _fdc3.HandleAddContextListener(addContextListenerRequest, new());
+ addContextListenerResponse.Should().NotBeNull();
+ addContextListenerResponse!.Success.Should().BeTrue();
+
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = originFdc3InstanceId,
+ ListenerId = addContextListenerResponse.Id!,
+ };
+
+ var response = await _fdc3.HandleRemoveContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Error.Should().Be(Fdc3DesktopAgentErrors.ListenerNotFound);
+ }
+
+ [Fact]
+ public async Task HandleRemoveContextListener_successfully_removes_context_listener()
+ {
+ await _fdc3.StartAsync(CancellationToken.None);
+
+ //TODO: should add some identifier to the query => "fdc3:" + instance.Manifest.Id
+ var origin = await _mockModuleLoader.Object.StartModule(new StartRequest("appId1"));
+ var originFdc3InstanceId = Fdc3InstanceIdRetriever.Get(origin);
+
+ var addContextListenerRequest = new AddContextListenerRequest
+ {
+ Fdc3InstanceId = originFdc3InstanceId,
+ ChannelId = "fdc3.channel.1",
+ ChannelType = ChannelType.User,
+ ContextType = null
+ };
+
+ var addContextListenerResponse = await _fdc3.HandleAddContextListener(addContextListenerRequest, new());
+ addContextListenerResponse.Should().NotBeNull();
+ addContextListenerResponse!.Success.Should().BeTrue();
+
+ var request = new RemoveContextListenerRequest
+ {
+ ContextType = null,
+ Fdc3InstanceId = originFdc3InstanceId,
+ ListenerId = addContextListenerResponse.Id!,
+ };
+
+ var response = await _fdc3.HandleRemoveContextListener(request, new());
+
+ response.Should().NotBeNull();
+ response!.Success.Should().BeTrue();
+ }
+
[Theory]
[ClassData(typeof(FindIntentTheoryData))]
public async Task HandleFindIntent_edge_case_tests(FindIntentTestCase testCase)
diff --git a/src/fdc3/js/composeui-fdc3/src/ComposeUIChannel.spec.ts b/src/fdc3/js/composeui-fdc3/src/ComposeUIChannel.spec.ts
index 52a9ae2e8..3f3609e44 100644
--- a/src/fdc3/js/composeui-fdc3/src/ComposeUIChannel.spec.ts
+++ b/src/fdc3/js/composeui-fdc3/src/ComposeUIChannel.spec.ts
@@ -18,6 +18,7 @@ import { ComposeUIContextListener } from './infrastructure/ComposeUIContextListe
import { ComposeUITopic } from './infrastructure/ComposeUITopic';
import { Channel, ChannelError, Context } from '@finos/fdc3';
import { ComposeUIDesktopAgent } from './ComposeUIDesktopAgent';
+import { Fdc3AddContextListenerResponse } from './infrastructure/messages/Fdc3AddContextListenerResponse';
const dummyChannelId = "dummyId";
let messageRouterClient: MessageRouter;
@@ -35,6 +36,22 @@ const contextMessageHandlerMock = jest.fn((_) => {
describe('Tests for ComposeUIChannel implementation API', () => {
beforeEach(() => {
+
+ window.composeui = {
+ fdc3: {
+ config: {
+ appId: "testAppId",
+ instanceId: "testInstanceId"
+ },
+ channelId : "test"
+ }
+ };
+
+ const response: Fdc3AddContextListenerResponse = {
+ success: true,
+ id: "testListenerId"
+ };
+
messageRouterClient = {
clientId: "dummy",
subscribe: jest.fn(() => {
@@ -47,7 +64,8 @@ describe('Tests for ComposeUIChannel implementation API', () => {
unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
registerService: jest.fn(() => { return Promise.resolve() }),
unregisterService: jest.fn(() => { return Promise.resolve() }),
- invoke: jest.fn(() => Promise.resolve(""))
+ invoke: jest.fn(() => { return Promise.resolve(`${JSON.stringify(undefined)}`) })
+ .mockImplementationOnce(() => Promise.resolve(`${JSON.stringify(response)}`))
};
testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClient);
@@ -60,27 +78,65 @@ describe('Tests for ComposeUIChannel implementation API', () => {
});
it('broadcast will set the lastContext to test instrument', async () => {
+ const messageRouterClientMock = {
+ clientId: "dummy",
+ subscribe: jest.fn(() => {
+ return Promise.resolve({ unsubscribe: () => { } });
+ }),
+
+ publish: jest.fn(() => { return Promise.resolve() }),
+ connect: jest.fn(() => { return Promise.resolve() }),
+ registerEndpoint: jest.fn(() => { return Promise.resolve() }),
+ unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
+ registerService: jest.fn(() => { return Promise.resolve() }),
+ unregisterService: jest.fn(() => { return Promise.resolve() }),
+ invoke: jest.fn(() => { return Promise.resolve(`${JSON.stringify(testInstrument)}`) })
+ };
+
+ testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClientMock);
+
await testChannel.broadcast(testInstrument);
const resultContext = await testChannel.getCurrentContext();
- expect(messageRouterClient.publish).toHaveBeenCalledTimes(1);
- expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
+ expect(messageRouterClientMock.publish).toHaveBeenCalledTimes(1);
+ expect(messageRouterClientMock.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
expect(resultContext).toMatchObject(testInstrument);
});
it('getCurrentContext will overwrite the lastContext of the same type', async () => {
- await testChannel.broadcast(testInstrument);
const testInstrument2 = {
type: 'fdc3.instrument',
id: {
ticker: 'SMSN'
}
};
+
+ const messageRouterClientMock = {
+ clientId: "dummy",
+ subscribe: jest.fn(() => {
+ return Promise.resolve({ unsubscribe: () => { } });
+ }),
+
+ publish: jest.fn(() => { return Promise.resolve() }),
+ connect: jest.fn(() => { return Promise.resolve() }),
+ registerEndpoint: jest.fn(() => { return Promise.resolve() }),
+ unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
+ registerService: jest.fn(() => { return Promise.resolve() }),
+ unregisterService: jest.fn(() => { return Promise.resolve() }),
+ invoke: jest.fn(() => { return Promise.resolve(`${JSON.stringify(testInstrument)}`) })
+ .mockImplementationOnce(() => {return Promise.resolve(`${JSON.stringify(testInstrument2)}`)})
+ .mockImplementationOnce(() => {return Promise.resolve(`${JSON.stringify(testInstrument2)}`)})
+ };
+
+ testChannel = new ComposeUIChannel(dummyChannelId, "user", messageRouterClientMock);
+
+ await testChannel.broadcast(testInstrument);
await testChannel.broadcast(testInstrument2);
+
const resultContext = await testChannel.getCurrentContext();
const resultContextWithContextType = await testChannel.getCurrentContext(testInstrument2.type);
- expect(messageRouterClient.publish).toBeCalledTimes(2);
- expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
- expect(messageRouterClient.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument2));
+ expect(messageRouterClientMock.publish).toBeCalledTimes(2);
+ expect(messageRouterClientMock.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument));
+ expect(messageRouterClientMock.publish).toHaveBeenCalledWith(ComposeUITopic.broadcast(dummyChannelId, "user"), JSON.stringify(testInstrument2));
expect(resultContext).toMatchObject(testInstrument2);
expect(resultContextWithContextType).toMatchObject>(testInstrument2);
});
@@ -96,11 +152,11 @@ describe('Tests for ComposeUIChannel implementation API', () => {
await testChannel.broadcast(testInstrument);
const resultListener = await testChannel.addContextListener('fdc3.instrument', contextMessageHandlerMock);
expect(resultListener).toBeInstanceOf(ComposeUIContextListener);
- expect(contextMessageHandlerMock).toHaveBeenCalledTimes(0); //as per the standard
+ expect(contextMessageHandlerMock).toHaveBeenCalledTimes(0);
});
// TODO: This doesn't test what it sais it tests
- it('addContextListener will treat contexType is ContextHandler as all types', async () => {
+ it('addContextListener will treat contextType is ContextHandler as all types', async () => {
const resultListener = await testChannel.addContextListener(null, contextMessageHandlerMock);
expect(resultListener).toBeInstanceOf(ComposeUIContextListener);
expect(messageRouterClient.subscribe).toBeCalledTimes(1);
diff --git a/src/fdc3/js/composeui-fdc3/src/ComposeUIContextListener.spec.ts b/src/fdc3/js/composeui-fdc3/src/ComposeUIContextListener.spec.ts
index d676bc15c..5b45f317a 100644
--- a/src/fdc3/js/composeui-fdc3/src/ComposeUIContextListener.spec.ts
+++ b/src/fdc3/js/composeui-fdc3/src/ComposeUIContextListener.spec.ts
@@ -14,6 +14,8 @@
import { jest } from '@jest/globals';
import { MessageRouter } from '@morgan-stanley/composeui-messaging-client';
import { ComposeUIContextListener } from './infrastructure/ComposeUIContextListener';
+import { Fdc3AddContextListenerResponse } from './infrastructure/messages/Fdc3AddContextListenerResponse';
+import { Fdc3RemoveContextListenerResponse } from './infrastructure/messages/Fdc3RemoveContextListenerResponse';
const dummyContext = { type: "dummyContextType" };
const dummyChannelId = "dummyId";
@@ -37,6 +39,21 @@ const contextMessageHandlerMock = jest.fn((something) => {
describe('Tests for ComposeUIContextListener implementation API', () => {
beforeEach(async () => {
+ window.composeui = {
+ fdc3: {
+ config: {
+ appId: "testAppId",
+ instanceId: "testInstanceId"
+ },
+ channelId : "test"
+ }
+ };
+
+ const response: Fdc3AddContextListenerResponse = {
+ success: true,
+ id: "testListenerId"
+ };
+
messageRouterClient = {
clientId: "dummy",
subscribe: jest.fn(() => {
@@ -49,11 +66,13 @@ describe('Tests for ComposeUIContextListener implementation API', () => {
unregisterEndpoint: jest.fn(() => { return Promise.resolve() }),
registerService: jest.fn(() => { return Promise.resolve() }),
unregisterService: jest.fn(() => { return Promise.resolve() }),
- invoke: jest.fn(() => { return Promise.resolve(JSON.stringify({ context: "", payload: `${JSON.stringify(dummyContext)}` })) })
+ invoke: jest.fn(() => { return Promise.resolve(`${JSON.stringify(undefined)}`) })
+ .mockImplementationOnce(() => Promise.resolve(`${JSON.stringify(response)}`))
+ .mockImplementationOnce(() => Promise.resolve(JSON.stringify({ context: "", payload: `${JSON.stringify(dummyContext)}` })))
};
- testListener = new ComposeUIContextListener(messageRouterClient, contextMessageHandlerMock, dummyChannelId, "user", "fdc3.instrument");
- await testListener.subscribe();
+ testListener = new ComposeUIContextListener(messageRouterClient, contextMessageHandlerMock, "fdc3.instrument");
+ await testListener.subscribe(dummyChannelId, "user");
});
it('subscribe will call messagerouter subscribe method', async () => {
@@ -66,6 +85,7 @@ describe('Tests for ComposeUIContextListener implementation API', () => {
});
it('handleContextMessage will be rejected with Error if unsubscribed', async () => {
+ testListener = new ComposeUIContextListener(messageRouterClient, contextMessageHandlerMock, "fdc3.instrument");
testListener.unsubscribe();
await expect(testListener.handleContextMessage(testInstrument))
.rejects
diff --git a/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.spec.ts b/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.spec.ts
index f8e47047a..2ecf30083 100644
--- a/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.spec.ts
+++ b/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.spec.ts
@@ -17,7 +17,7 @@ import { MessageRouter } from '@morgan-stanley/composeui-messaging-client';
import { ComposeUIContextListener } from './infrastructure/ComposeUIContextListener';
import { ComposeUIDesktopAgent } from './ComposeUIDesktopAgent';
import { ComposeUITopic } from './infrastructure/ComposeUITopic';
-import { Channel, ChannelError, DesktopAgent } from '@finos/fdc3';
+import { Channel, ChannelError, ContextHandler, DesktopAgent } from '@finos/fdc3';
import { ComposeUIErrors } from './infrastructure/ComposeUIErrors';
import { ChannelFactory } from './infrastructure/ChannelFactory';
import { ComposeUIPrivateChannel } from './infrastructure/ComposeUIPrivateChannel';
@@ -78,6 +78,7 @@ describe('Tests for ComposeUIDesktopAgent implementation API', () => {
createAppChannel: jest.fn(() => Promise.reject("Not implemented")),
joinUserChannel: jest.fn(() => Promise.resolve(new ComposeUIChannel(dummyChannelId, "user", messageRouterClient))),
getUserChannels: jest.fn(() => Promise.reject("Not implemented")),
+ getContextListener: jest.fn((channel: Channel, handler: ContextHandler, contextType?: string) => {return Promise.resolve(new ComposeUIContextListener(messageRouterClient, handler, contextType))})
};
desktopAgent = new ComposeUIDesktopAgent(messageRouterClient, channelFactory);
@@ -104,20 +105,6 @@ describe('Tests for ComposeUIDesktopAgent implementation API', () => {
.toThrow("The current channel has not been set.");
});
- it('addContextListener will trigger messageRouter subscribe method', async () => {
- const resultListener = await desktopAgent.addContextListener("fdc3.instrument", contextMessageHandlerMock);
- expect(resultListener).toBeInstanceOf(ComposeUIContextListener);
- expect(messageRouterClient.subscribe).toBeCalledTimes(1);
- });
-
- it('addContextListener will fail as the current channel is not defined', async () => {
- await desktopAgent.leaveCurrentChannel();
- await expect(desktopAgent.addContextListener("fdc3.instrument", contextMessageHandlerMock))
- .rejects
- .toThrow("The current channel has not been set.");
- expect(messageRouterClient.subscribe).toBeCalledTimes(0);
- });
-
it('default channel can be retrieved', async () => {
var result = await desktopAgent.getCurrentChannel();
expect(result).toMatchObject>({ id: dummyChannelId, type: "user" });
diff --git a/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.ts b/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.ts
index 5dd9de99d..aabba49fd 100644
--- a/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.ts
+++ b/src/fdc3/js/composeui-fdc3/src/ComposeUIDesktopAgent.ts
@@ -42,7 +42,7 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
private userChannels: Channel[] = [];
private privateChannels: Channel[] = [];
private currentChannel?: Channel;
- private currentChannelListeners: ComposeUIContextListener[] = [];
+ private topLevelContextListeners: ComposeUIContextListener[] = [];
private intentListeners: Listener[] = [];
private channelFactory: ChannelFactory;
private intentsClient: IntentsClient;
@@ -101,25 +101,20 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
}
public async addContextListener(contextType?: string | null | ContextHandler, handler?: ContextHandler): Promise {
- if (!this.currentChannel) {
- throw new Error(ComposeUIErrors.CurrentChannelNotSet);
- }
-
if (contextType && typeof contextType != 'string') {
handler = contextType;
contextType = null;
}
- const listener = await this.currentChannel!.addContextListener(contextType ?? null, handler!);
-
- const lastContext = await this.currentChannel!.getCurrentContext(contextType ?? undefined)
+ const listener = await this.channelFactory.getContextListener(this.currentChannel, handler, contextType);
+ this.topLevelContextListeners.push(listener);
- if (lastContext) {
- //TODO: timing issue
- setTimeout(async() => await listener.handleContextMessage(lastContext), 100);
+ if (!this.currentChannel) {
+ return listener;
}
- this.currentChannelListeners.push(listener);
+ await this.getLastContext(listener);
+
return listener;
}
@@ -127,7 +122,6 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
return await this.channelFactory.getUserChannels();
}
- //TODO: add pending context listeners which were registered via the fdc3.addContextListener
public async joinUserChannel(channelId: string): Promise {
if (this.currentChannel) {
//DesktopAgnet clients can listen on only one channel
@@ -145,6 +139,11 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
this.addChannel(channel);
this.currentChannel = channel;
+
+ for (const listener of this.topLevelContextListeners) {
+ await listener.subscribe(this.currentChannel.id, this.currentChannel.type);
+ await this.getLastContext(listener);
+ }
}
public async getOrCreateChannel(channelId: string): Promise {
@@ -167,14 +166,13 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
return this.currentChannel ?? null;
}
- //TODO: add messageRouter message that we are leaving the current channel to notify the backend.
- //TODO: leave the current channel's listeners added via fdc3.addContextListener.
public async leaveCurrentChannel(): Promise {
+ //The context listeners, that have been added through the `fdc3.addContextListener()` should unsubscribe
+ for (const listener of this.topLevelContextListeners) {
+ await listener.unsubscribe();
+ }
+
this.currentChannel = undefined;
- this.currentChannelListeners.forEach(listener => {
- listener.unsubscribe();
- });
- this.currentChannelListeners = [];
}
public async getInfo(): Promise {
@@ -211,4 +209,13 @@ export class ComposeUIDesktopAgent implements DesktopAgent {
break;
}
}
+
+ private async getLastContext(listener: ComposeUIContextListener) : Promise {
+ const lastContext = await this.currentChannel!.getCurrentContext(listener.contextType);
+
+ if (lastContext) {
+ //TODO: timing issue
+ setTimeout(async() => await listener.handleContextMessage(lastContext), 100);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/ChannelFactory.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/ChannelFactory.ts
index d8df17228..7e984628e 100644
--- a/src/fdc3/js/composeui-fdc3/src/infrastructure/ChannelFactory.ts
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/ChannelFactory.ts
@@ -11,7 +11,7 @@
*
*/
-import { Channel, IntentHandler, Listener, PrivateChannel } from "@finos/fdc3";
+import { Channel, ContextHandler, IntentHandler, Listener, PrivateChannel } from "@finos/fdc3";
import { ChannelType } from "./ChannelType";
export interface ChannelFactory {
@@ -21,4 +21,5 @@ export interface ChannelFactory {
joinUserChannel(channelId: string): Promise;
getUserChannels(): Promise;
getIntentListener(intent: string, handler: IntentHandler): Promise;
+ getContextListener(channel?: Channel, handler?: ContextHandler, contextType?: string | null): Promise;
}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIChannel.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIChannel.ts
index fe2dbfbcf..083deac01 100644
--- a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIChannel.ts
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIChannel.ts
@@ -17,6 +17,9 @@ import { ChannelType } from "./ChannelType";
import { ComposeUIContextListener } from "./ComposeUIContextListener";
import { Fdc3GetCurrentContextRequest } from "./messages/Fdc3GetCurrentContextRequest";
import { ComposeUITopic } from "./ComposeUITopic";
+import { ComposeUIErrors } from "./ComposeUIErrors";
+import { Fdc3AddContextListenerResponse } from "./messages/Fdc3AddContextListenerResponse";
+import { Fdc3AddContextListenerRequest } from "./messages/Fdc3AddContextListenerRequest";
export class ComposeUIChannel implements Channel {
id: string;
@@ -43,7 +46,6 @@ export class ComposeUIChannel implements Channel {
await this.messageRouterClient.publish(topic, JSON.stringify(context));
}
- //TODO add error
public async getCurrentContext(contextType?: string | undefined): Promise {
const message = JSON.stringify(new Fdc3GetCurrentContextRequest(contextType));
const response = await this.messageRouterClient.invoke(ComposeUITopic.getCurrentContext(this.id, this.type), message);
@@ -77,8 +79,8 @@ export class ComposeUIChannel implements Channel {
contextType = null;
}
- const listener = new ComposeUIContextListener(this.messageRouterClient, handler, this.id, this.type, contextType);
- await listener.subscribe();
+ const listener = new ComposeUIContextListener(this.messageRouterClient, handler, contextType);
+ await listener.subscribe(this.id, this.type);
return listener;
}
}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIContextListener.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIContextListener.ts
index 8e7a38c67..0b615d11d 100644
--- a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIContextListener.ts
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIContextListener.ts
@@ -11,34 +11,35 @@
*
*/
-import { Context, ContextHandler, Listener } from "@finos/fdc3";
+import { Context, ContextHandler, Listener, ResultError } from "@finos/fdc3";
import { MessageRouter, TopicMessage } from "@morgan-stanley/composeui-messaging-client";
import { ChannelType } from "./ChannelType";
import { Unsubscribable } from "rxjs";
import { ComposeUITopic } from "./ComposeUITopic";
+import { Fdc3RemoveContextListenerRequest } from "./messages/Fdc3RemoveContextListenerRequest";
+import { Fdc3RemoveContextListenerResponse } from "./messages/Fdc3RemoveContextListenerResponse";
+import { ComposeUIErrors } from "./ComposeUIErrors";
+import { Fdc3AddContextListenerRequest } from "./messages/Fdc3AddContextListenerRequest";
+import { Fdc3AddContextListenerResponse } from "./messages/Fdc3AddContextListenerResponse";
export class ComposeUIContextListener implements Listener {
private readonly messageRouterClient: MessageRouter;
private unsubscribable?: Unsubscribable;
private readonly handler: ContextHandler;
- private readonly channelId: string;
- private readonly channelType: ChannelType;
public readonly contextType?: string;
private isSubscribed: boolean = false;
+ private id?: string;
private unsubscribeCallback?: (x: ComposeUIContextListener) => void;
- constructor(messageRouterClient: MessageRouter, handler: ContextHandler, channelId: string, channelType: ChannelType, contextType?: string) {
+ constructor(messageRouterClient: MessageRouter, handler: ContextHandler, contextType?: string) {
this.messageRouterClient = messageRouterClient;
this.handler = handler;
-
- this.channelId = channelId;
- this.channelType = channelType;
-
this.contextType = contextType;
}
- public async subscribe(): Promise {
- const subscribeTopic = ComposeUITopic.broadcast(this.channelId, this.channelType);
+ public async subscribe(channelId: string, channelType: ChannelType): Promise {
+ await this.registerContextListener(channelId, channelType);
+ const subscribeTopic = ComposeUITopic.broadcast(channelId, channelType);
this.unsubscribable = await this.messageRouterClient.subscribe(subscribeTopic, (topicMessage: TopicMessage) => {
if (topicMessage.context.sourceId == this.messageRouterClient.clientId) return;
//TODO: integration test
@@ -64,14 +65,59 @@ export class ComposeUIContextListener implements Listener {
this.unsubscribeCallback = unsubscribeCallback;
}
- public unsubscribe(): void {
+ public async unsubscribe(): Promise {
if (!this.unsubscribable || !this.isSubscribed) {
return;
}
+
+ try {
+ await this.leaveChannel();
+ } catch(err) {
+ console.log(err);
+ }
+
this.unsubscribable.unsubscribe();
this.isSubscribed = false;
+
if (this.unsubscribeCallback) {
this.unsubscribeCallback(this);
}
}
-}
\ No newline at end of file
+
+ private async registerContextListener(channelId: string, channelType: ChannelType) :Promise{
+ const request = new Fdc3AddContextListenerRequest(window.composeui.fdc3.config?.instanceId!, this.contextType, channelId, channelType);
+ const response = await this.messageRouterClient.invoke(ComposeUITopic.addContextListener(), JSON.stringify(request));
+
+ if (!response) {
+ throw new Error(ComposeUIErrors.NoAnswerWasProvided);
+ }
+
+ const result = JSON.parse(response);
+ if (result.error) {
+ throw new Error(result.error);
+ } else if (!result.success) {
+ throw new Error(ComposeUIErrors.SubscribeFailure);
+ }
+
+ this.id = result.id!
+ }
+
+ private async leaveChannel() : Promise {
+ const request = new Fdc3RemoveContextListenerRequest(window.composeui.fdc3.config?.instanceId!, this.id!, this.contextType);
+ const result = await this.messageRouterClient.invoke(ComposeUITopic.removeContextListener(), JSON.stringify(request));
+ if (!result) {
+ throw new Error(ComposeUIErrors.NoAnswerWasProvided);
+ }
+
+ const response = JSON.parse(result);
+ if (response.error) {
+ throw new Error(response.error);
+ }
+
+ if (!response.success) {
+ throw new Error(ComposeUIErrors.UnsubscribeFailure);
+ }
+
+ return;
+ }
+}
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIErrors.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIErrors.ts
index c9e61d52c..f9762881d 100644
--- a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIErrors.ts
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUIErrors.ts
@@ -15,6 +15,6 @@ export enum ComposeUIErrors {
NoAnswerWasProvided = 'No answer was provided by the DesktopAgent backend.',
InstanceIdNotFound = 'InstanceId was not found on window object. To run Fdc3\'s ComposeUI implementation instance config should be set on window config.',
CurrentChannelNotSet = 'The current channel has not been set.',
- UnsubscribeFailure = 'The IntentListener could not unsubscribe.',
- SubscribeFailure = 'The IntentListener could not subscribe.'
+ UnsubscribeFailure = 'The Listener could not unsubscribe.',
+ SubscribeFailure = 'The Listener could not subscribe.'
}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUITopic.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUITopic.ts
index 5753013ac..c8cac912a 100644
--- a/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUITopic.ts
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/ComposeUITopic.ts
@@ -36,6 +36,8 @@ export class ComposeUITopic {
private static readonly getInfoSuffix = "getInfo";
private static readonly findInstancesSuffix = "findInstances";
private static readonly getAppMetadataSuffix = "getAppMetadata";
+ private static readonly addContextListenerSuffix = "addContextListener";
+ private static readonly removeContextListenerSuffix = "removeContextListener";
public static broadcast(channelId: string, channelType: ChannelType = "user"): string {
return `${this.getChannelsTopicRootWithChannelId(channelId, channelType)}/${this.broadcastSuffix}`;
@@ -112,6 +114,14 @@ export class ComposeUITopic {
public static getAppMetadata(): string {
return `${this.topicRoot}/${this.getAppMetadataSuffix}`;
}
+
+ public static addContextListener(): string {
+ return `${this.topicRoot}/${this.addContextListenerSuffix}`;
+ }
+
+ public static removeContextListener(): string {
+ return `${this.topicRoot}/${this.removeContextListenerSuffix}`;
+ }
private static getChannelsTopicRootWithChannelId(channelId: string, channelType: ChannelType): string {
return `${this.getChannelsTopicRoot(channelType)}/${channelId}`;
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/MessageRouterChannelFactory.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/MessageRouterChannelFactory.ts
index a5f1dc23b..1db0ff4a0 100644
--- a/src/fdc3/js/composeui-fdc3/src/infrastructure/MessageRouterChannelFactory.ts
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/MessageRouterChannelFactory.ts
@@ -11,7 +11,7 @@
*
*/
-import { ChannelError, IntentHandler, Listener, PrivateChannel } from "@finos/fdc3";
+import { ChannelError, ContextHandler, IntentHandler, Listener, PrivateChannel } from "@finos/fdc3";
import { MessageRouter } from "@morgan-stanley/composeui-messaging-client";
import { Channel } from "@finos/fdc3";
import { ChannelFactory } from "./ChannelFactory";
@@ -33,7 +33,10 @@ import { Fdc3GetUserChannelsRequest } from "./messages/Fdc3GetUserChannelsReques
import { Fdc3GetUserChannelsResponse } from "./messages/Fdc3GetUserChannelsResponse";
import { Fdc3JoinUserChannelRequest } from "./messages/Fdc3JoinUserChannelRequest";
import { Fdc3JoinUserChannelResponse } from "./messages/Fdc3JoinUserChannelResponse";
+import { Fdc3AddContextListenerRequest } from "./messages/Fdc3AddContextListenerRequest";
+import { Fdc3AddContextListenerResponse } from "./messages/Fdc3AddContextListenerResponse";
import { ChannelItem } from "./ChannelItem";
+import { ComposeUIContextListener } from "./ComposeUIContextListener";
export class MessageRouterChannelFactory implements ChannelFactory {
private messageRouterClient: MessageRouter;
@@ -163,4 +166,14 @@ export class MessageRouterChannelFactory implements ChannelFactory {
return listener;
}
+
+ public async getContextListener(channel?: Channel, handler?: ContextHandler, contextType?: string | null): Promise {
+ if (channel) {
+ const listener = await channel.addContextListener(contextType ?? null, handler!);
+ return listener;
+ }
+
+ const listener = new ComposeUIContextListener(this.messageRouterClient, handler!, contextType ?? undefined);
+ return listener;
+ }
}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3AddContextListenerRequest.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3AddContextListenerRequest.ts
new file mode 100644
index 000000000..bb872a11a
--- /dev/null
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3AddContextListenerRequest.ts
@@ -0,0 +1,22 @@
+/*
+ * Morgan Stanley makes this available to you under the Apache License,
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership. Unless required by applicable law or agreed
+ * to in writing, software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+import { ChannelType } from "../ChannelType";
+
+export class Fdc3AddContextListenerRequest {
+ constructor(
+ public readonly fdc3InstanceId: string,
+ public readonly contextType?: string | null,
+ public readonly channelId?: string,
+ public readonly channelType?: ChannelType
+ ) {}
+}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3AddContextListenerResponse.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3AddContextListenerResponse.ts
new file mode 100644
index 000000000..604916b6d
--- /dev/null
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3AddContextListenerResponse.ts
@@ -0,0 +1,17 @@
+/*
+ * Morgan Stanley makes this available to you under the Apache License,
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership. Unless required by applicable law or agreed
+ * to in writing, software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+export interface Fdc3AddContextListenerResponse {
+ id?: string;
+ error?: string;
+ success: boolean;
+}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3RemoveContextListenerRequest.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3RemoveContextListenerRequest.ts
new file mode 100644
index 000000000..d56ea547f
--- /dev/null
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3RemoveContextListenerRequest.ts
@@ -0,0 +1,19 @@
+/*
+ * Morgan Stanley makes this available to you under the Apache License,
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership. Unless required by applicable law or agreed
+ * to in writing, software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+export class Fdc3RemoveContextListenerRequest {
+ constructor(
+ public readonly fdc3InstanceId: string,
+ public readonly listenerId: string,
+ public readonly contextType?: string,
+ ) {}
+}
\ No newline at end of file
diff --git a/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3RemoveContextListenerResponse.ts b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3RemoveContextListenerResponse.ts
new file mode 100644
index 000000000..b7e400af6
--- /dev/null
+++ b/src/fdc3/js/composeui-fdc3/src/infrastructure/messages/Fdc3RemoveContextListenerResponse.ts
@@ -0,0 +1,17 @@
+/*
+ * Morgan Stanley makes this available to you under the Apache License,
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership. Unless required by applicable law or agreed
+ * to in writing, software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+
+export interface Fdc3RemoveContextListenerResponse {
+ error?: string;
+ success: boolean;
+}
\ No newline at end of file