Skip to content

Commit 8e74163

Browse files
committed
Fixing PrivateChannels
1 parent bd8c40b commit 8e74163

17 files changed

+221
-72
lines changed

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Channels/Channel.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal abstract class Channel : IAsyncDisposable
3333
private string? _lastContext = null;
3434
private IAsyncDisposable? _broadcastSubscription;
3535
private int _disposed = 0;
36-
private JsonSerializerOptions _jsonSerializerOptions;
36+
protected JsonSerializerOptions JsonSerializerOptions { get; }
3737

3838
private IAsyncDisposable? _getCurrentContextService;
3939

@@ -43,7 +43,7 @@ protected Channel(string id, IMessaging messagingService, JsonSerializerOptions
4343
MessagingService = messagingService;
4444
_logger = logger;
4545
_topics = topics;
46-
_jsonSerializerOptions = jsonSerializerOptions;
46+
JsonSerializerOptions = jsonSerializerOptions;
4747
}
4848

4949
public async ValueTask Connect()
@@ -53,7 +53,7 @@ public async ValueTask Connect()
5353
throw new ObjectDisposedException(nameof(Channel));
5454
}
5555

56-
_getCurrentContextService = await MessagingService.RegisterJsonServiceAsync<GetCurrentContextRequest, string?>(_topics.GetCurrentContext, GetCurrentContext, _jsonSerializerOptions);
56+
_getCurrentContextService = await MessagingService.RegisterJsonServiceAsync<GetCurrentContextRequest, string?>(_topics.GetCurrentContext, GetCurrentContext, JsonSerializerOptions);
5757

5858
var broadcastSubscription = MessagingService.SubscribeAsync(_topics.Broadcast, HandleBroadcast);
5959

@@ -100,7 +100,7 @@ internal ValueTask HandleBroadcast(string? payloadBuffer)
100100
}
101101

102102
_contexts.AddOrUpdate(
103-
contextType,
103+
contextType!,
104104
_ => payloadBuffer,
105105
(_, _) => payloadBuffer);
106106

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Channels/PrivateChannel.cs

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,31 @@
1212
* and limitations under the License.
1313
*/
1414

15+
using System.Runtime.CompilerServices;
1516
using System.Text.Json;
1617
using Microsoft.Extensions.Logging;
1718
using Microsoft.Extensions.Logging.Abstractions;
19+
using MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
1820
using MorganStanley.ComposeUI.Messaging.Abstractions;
1921

2022
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Channels;
2123

2224
internal class PrivateChannel : Channel
2325
{
24-
public PrivateChannel(string id, IMessaging messagingService, JsonSerializerOptions jsonSerializerOptions, ILogger<PrivateChannel>? logger, string instanceId)
26+
private readonly string _eventsTopic;
27+
28+
public PrivateChannel(string id, IMessaging messagingService, JsonSerializerOptions jsonSerializerOptions, ILogger<PrivateChannel>? logger)
2529
: base(id, messagingService, jsonSerializerOptions, (ILogger?) logger ?? NullLogger.Instance, Fdc3Topic.PrivateChannel(id))
2630
{
27-
InstanceId = instanceId;
31+
_eventsTopic = Fdc3Topic.PrivateChannel(Id).Events;
2832
}
2933

30-
public string InstanceId { get; }
3134
protected override string ChannelTypeName => "PrivateChannel";
3235

33-
public async Task Close(CancellationToken cancellationToken = default)
36+
public async Task Close(string instanceId, CancellationToken cancellationToken = default)
3437
{
35-
var topic = Fdc3Topic.PrivateChannel(Id).Events;
36-
var payload = "{\"event\": \"disconnected\"}";
37-
var tcs = new TaskCompletionSource<bool>();
38-
39-
var subscription = await MessagingService.SubscribeAsync(topic, async buffer =>
40-
{
41-
if (buffer == null || !buffer.Contains("disconnected"))
42-
{
43-
LogUnexpectedMessage(buffer ?? string.Empty);
44-
}
45-
46-
tcs.TrySetResult(true);
47-
await Task.CompletedTask;
48-
}, cancellationToken);
49-
50-
await MessagingService.PublishAsync(topic, payload, cancellationToken: cancellationToken);
51-
52-
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
53-
{
54-
await tcs.Task;
55-
}
38+
var payload = PrivateChannelInternalEvents.Disconnected(instanceId);
5639

57-
await subscription.DisposeAsync();
40+
await MessagingService.PublishJsonAsync(_eventsTopic, payload, JsonSerializerOptions, cancellationToken: cancellationToken);
5841
}
5942
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Morgan Stanley makes this available to you under the Apache License,
3+
* Version 2.0 (the "License"). You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0.
6+
*
7+
* See the NOTICE file distributed with this work for additional information
8+
* regarding copyright ownership. Unless required by applicable law or agreed
9+
* to in writing, software distributed under the License is distributed on an
10+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions
12+
* and limitations under the License.
13+
*/
14+
15+
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
16+
17+
internal sealed class JoinPrivateChannelRequest
18+
{
19+
/// <summary>
20+
/// Uniques identifier of the channel.
21+
/// </summary>
22+
public string ChannelId { get; set; }
23+
24+
/// <summary>
25+
/// Unique identifier of the app which sent the request.
26+
/// </summary>
27+
public string InstanceId { get; set; }
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Morgan Stanley makes this available to you under the Apache License,
3+
* Version 2.0 (the "License"). You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0.
6+
*
7+
* See the NOTICE file distributed with this work for additional information
8+
* regarding copyright ownership. Unless required by applicable law or agreed
9+
* to in writing, software distributed under the License is distributed on an
10+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions
12+
* and limitations under the License.
13+
*/
14+
15+
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;
16+
17+
internal sealed class JoinPrivateChannelResponse
18+
{
19+
/// <summary>
20+
/// Error while executing the JoinPrivateChannel call.
21+
/// </summary>
22+
public string? Error { get; set; }
23+
24+
/// <summary>
25+
/// Indicates if the request was successful.
26+
/// </summary>
27+
public bool Success { get; set; }
28+
29+
public static JoinPrivateChannelResponse Joined => new() { Success = true };
30+
public static JoinPrivateChannelResponse Failed(string error) => new() { Success = false, Error = error };
31+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts
6+
{
7+
/// <summary>
8+
/// Represents an internal event on a Private Channel
9+
/// </summary>
10+
internal sealed class PrivateChannelInternalEvents
11+
{
12+
public string Event { get; set; }
13+
public string InstanceId { get; set; }
14+
public string? ContextType { get; set; }
15+
16+
public static PrivateChannelInternalEvents Disconnected(string instanceId) => new() { Event = "disconnected", InstanceId = instanceId };
17+
}
18+
}

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Exceptions/Fdc3DesktopAgentErrors.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,9 @@ public static class Fdc3DesktopAgentErrors
6060
/// Indicates that during intent resolution the requested app id exists but the instance does not
6161
/// </summary>
6262
public const string TargetInstanceUnavailable = nameof(TargetInstanceUnavailable);
63+
64+
/// <summary>
65+
/// Indicates that the private channel a client tried to join cannot be found
66+
/// </summary>
67+
public const string PrivateChannelNotFound = nameof(PrivateChannelNotFound);
6368
}

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3DesktopAgent.cs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -126,25 +126,23 @@ public Fdc3DesktopAgent(
126126
}
127127
}
128128

129-
public async ValueTask AddPrivateChannel(Func<string, PrivateChannel> addPrivateChannelFactory, string privateChannelId)
129+
public async ValueTask CreateOrJoinPrivateChannel(Func<string, PrivateChannel> addPrivateChannelFactory, string privateChannelId, string instanceId)
130130
{
131131
if (privateChannelId == null)
132132
{
133-
_logger.LogError($"Could not create private channel while executing {nameof(AddPrivateChannel)} due to private channel id is null.");
133+
_logger.LogError($"Could not create private channel while executing {nameof(CreateOrJoinPrivateChannel)} due to private channel id is null.");
134134
return;
135135
}
136-
137-
//Checking if the endpoint is already registered, because it can cause issues while registering services storing the latest context messages, etc on the Channel objects.
138-
if (_privateChannels.TryGetValue(privateChannelId, out var privateChannel))
139-
{
140-
return;
141-
}
142-
136+
PrivateChannel? privateChannel = null;
143137
try
144138
{
145-
privateChannel = _privateChannels.GetOrAdd(privateChannelId, addPrivateChannelFactory(privateChannelId));
139+
// Check if the channel already exists and create if it does not.
140+
if (!_privateChannels.TryGetValue(privateChannelId, out privateChannel))
141+
{
142+
privateChannel = _privateChannels.GetOrAdd(privateChannelId, addPrivateChannelFactory(privateChannelId));
143+
}
146144

147-
SafeAddToPrivateChannelsDictionary(privateChannel);
145+
SafeAddToPrivateChannelsDictionary(instanceId, privateChannel);
148146

149147
await privateChannel!.Connect();
150148
}
@@ -157,21 +155,24 @@ public async ValueTask AddPrivateChannel(Func<string, PrivateChannel> addPrivate
157155
}
158156
catch (Exception exception)
159157
{
160-
_logger.LogError(exception, $"Exception thrown while executing {nameof(AddPrivateChannel)}.");
158+
_logger.LogError(exception, $"Exception thrown while executing {nameof(CreateOrJoinPrivateChannel)}.");
161159
_privateChannels.TryRemove(privateChannelId, out _);
162-
SafeRemoveFromPrivateChannelsDictionary(privateChannel!);
160+
if (privateChannel != null)
161+
{
162+
SafeRemoveFromPrivateChannelsDictionary(instanceId, privateChannel);
163+
}
163164
throw;
164165
}
165166
}
166167

167-
private void SafeAddToPrivateChannelsDictionary(PrivateChannel privateChannel)
168+
private void SafeAddToPrivateChannelsDictionary(string instanceId, PrivateChannel privateChannel)
168169
{
169170
lock (_privateChannelsDictionaryLock)
170171
{
171-
if (!_privateChannelsByInstanceId.TryGetValue(privateChannel.InstanceId, out var privateChannels))
172+
if (!_privateChannelsByInstanceId.TryGetValue(instanceId, out var privateChannels))
172173
{
173174
privateChannels = [privateChannel];
174-
_privateChannelsByInstanceId[privateChannel.InstanceId] = privateChannels;
175+
_privateChannelsByInstanceId[instanceId] = privateChannels;
175176
}
176177
else if (!privateChannels!.Contains(privateChannel))
177178
{
@@ -180,13 +181,19 @@ private void SafeAddToPrivateChannelsDictionary(PrivateChannel privateChannel)
180181
}
181182
}
182183

183-
private void SafeRemoveFromPrivateChannelsDictionary(PrivateChannel privateChannel)
184+
private void SafeRemoveFromPrivateChannelsDictionary(string instanceId, PrivateChannel privateChannel)
184185
{
185186
lock (_privateChannelsDictionaryLock)
186187
{
187-
if (_privateChannelsByInstanceId.TryGetValue(privateChannel.InstanceId, out var privateChannels))
188+
if (!_privateChannelsByInstanceId.TryGetValue(instanceId, out var privateChannels))
189+
{
190+
return;
191+
}
192+
193+
privateChannels.Remove(privateChannel);
194+
if (privateChannels.Count == 0)
188195
{
189-
privateChannels.Remove(privateChannel);
196+
_privateChannelsByInstanceId.Remove(instanceId);
190197
}
191198
}
192199
}
@@ -1491,8 +1498,9 @@ public async ValueTask CloseModule(string instanceId, CancellationToken cancella
14911498
{
14921499
foreach (var channel in privateChannels)
14931500
{
1494-
await channel.Close(cancellationToken).ConfigureAwait(false);
1501+
await channel.Close(instanceId, cancellationToken).ConfigureAwait(false);
14951502
}
1503+
_privateChannelsByInstanceId.Remove(instanceId);
14961504
}
14971505
}
14981506
}

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Fdc3Topic.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ internal static class Fdc3Topic
3131
internal static string CreateAppChannel => TopicRoot + "createAppChannel";
3232
internal static string GetUserChannels => TopicRoot + "getUserChannels";
3333
internal static string JoinUserChannel => TopicRoot + "joinUserChannel";
34+
internal static string JoinPrivateChannel => TopicRoot + "joinPrivateChannel";
3435
internal static string GetInfo => TopicRoot + "getInfo";
3536
internal static string FindInstances => TopicRoot + "findInstances";
3637
internal static string GetAppMetadata => TopicRoot + "getAppMetadata";

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/Fdc3DesktopAgentMessagingService.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,15 @@ await _messaging.PublishJsonAsync(
133133

134134
internal async ValueTask<CreatePrivateChannelResponse?> HandleCreatePrivateChannel(CreatePrivateChannelRequest? request)
135135
{
136+
if (request == null)
137+
{
138+
return CreatePrivateChannelResponse.Failed(Fdc3DesktopAgentErrors.PayloadNull);
139+
}
140+
136141
try
137142
{
138143
var privateChannelId = Guid.NewGuid().ToString();
139-
await _desktopAgent.AddPrivateChannel((channelId) => new PrivateChannel(channelId, _messaging, _jsonSerializerOptions, _loggerFactory.CreateLogger<PrivateChannel>(), request.InstanceId), privateChannelId);
144+
await _desktopAgent.CreateOrJoinPrivateChannel((channelId) => new PrivateChannel(channelId, _messaging, _jsonSerializerOptions, _loggerFactory.CreateLogger<PrivateChannel>()), privateChannelId, request.InstanceId);
140145

141146
return CreatePrivateChannelResponse.Created(privateChannelId);
142147
}
@@ -147,6 +152,23 @@ await _messaging.PublishJsonAsync(
147152
}
148153
}
149154

155+
internal async ValueTask<JoinPrivateChannelResponse?> HandleJoinPrivateChannel(JoinPrivateChannelRequest? request)
156+
{
157+
if (request == null)
158+
{
159+
return JoinPrivateChannelResponse.Failed(Fdc3DesktopAgentErrors.PayloadNull);
160+
}
161+
try
162+
{
163+
await _desktopAgent.CreateOrJoinPrivateChannel((channelId) => throw new Fdc3DesktopAgentException(Fdc3DesktopAgentErrors.PrivateChannelNotFound, "The private channel could not be found"), request.ChannelId, request.InstanceId);
164+
return JoinPrivateChannelResponse.Joined;
165+
}
166+
catch (Exception ex)
167+
{
168+
return JoinPrivateChannelResponse.Failed(ex.Message);
169+
}
170+
}
171+
150172
internal async ValueTask<CreateAppChannelResponse?> HandleCreateAppChannel(
151173
CreateAppChannelRequest? request)
152174
{
@@ -261,6 +283,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
261283
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<StoreIntentResultRequest, StoreIntentResultResponse>(Fdc3Topic.SendIntentResult, HandleStoreIntentResult, _jsonSerializerOptions));
262284
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<IntentListenerRequest, IntentListenerResponse>(Fdc3Topic.AddIntentListener, HandleAddIntentListener, _jsonSerializerOptions));
263285
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<CreatePrivateChannelRequest, CreatePrivateChannelResponse>(Fdc3Topic.CreatePrivateChannel, HandleCreatePrivateChannel, _jsonSerializerOptions));
286+
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<JoinPrivateChannelRequest, JoinPrivateChannelResponse>(Fdc3Topic.JoinPrivateChannel, HandleJoinPrivateChannel, _jsonSerializerOptions));
264287
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<CreateAppChannelRequest, CreateAppChannelResponse>(Fdc3Topic.CreateAppChannel, HandleCreateAppChannel, _jsonSerializerOptions));
265288
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<GetUserChannelsRequest, GetUserChannelsResponse>(Fdc3Topic.GetUserChannels, HandleGetUserChannels, _jsonSerializerOptions));
266289
_registeredServices.Add(await _messaging.RegisterJsonServiceAsync<JoinUserChannelRequest, JoinUserChannelResponse>(Fdc3Topic.JoinUserChannel, HandleJoinUserChannel, _jsonSerializerOptions));

src/fdc3/dotnet/DesktopAgent/src/MorganStanley.ComposeUI.DesktopAgent/Infrastructure/Internal/IFdc3DesktopAgentBridge.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ internal interface IFdc3DesktopAgentBridge
4848
/// <param name="addPrivateChannelFactory"></param>
4949
/// <param name="privateChannelId"></param>
5050
/// <returns></returns>
51-
public ValueTask AddPrivateChannel(Func<string, PrivateChannel> addPrivateChannelFactory, string privateChannelId);
51+
public ValueTask CreateOrJoinPrivateChannel(Func<string, PrivateChannel> addPrivateChannelFactory, string privateChannelId, string instanceId);
5252

5353
/// <summary>
5454
/// Handles the AddAppChannel call in the bridge.

0 commit comments

Comments
 (0)