diff --git a/src/Examples/WpfFdc3/Fdc3/DesktopAgent.cs b/src/Examples/WpfFdc3/Fdc3/DesktopAgent.cs index 4bc48b6..28906c2 100644 --- a/src/Examples/WpfFdc3/Fdc3/DesktopAgent.cs +++ b/src/Examples/WpfFdc3/Fdc3/DesktopAgent.cs @@ -25,6 +25,7 @@ internal class DesktopAgent : IDesktopAgent { private readonly List _channels = new List(); private IChannel? _currentChannel; + private List _listeners = new List(); public DesktopAgent() { @@ -41,6 +42,16 @@ public Task AddIntentListener(string intent, IntentHandler hand throw new NotImplementedException(); } + public Task AddEventListener(string? eventType, Fdc3EventHandler handler) + { + return Task.Run(() => + { + EventListener listener = new EventListener(eventType, handler); + _listeners.Add(listener); + return listener; + }); + } + public Task Broadcast(IContext context) { if (_currentChannel != null) @@ -125,12 +136,30 @@ public Task JoinUserChannel(string channelId) { throw new Exception(ChannelError.NoChannelFound); } + + // Notify listeners of AddEventListener of channel changed + _listeners.ForEach(listener => { + if (listener.EventType == Fdc3EventType.UserChannelChanged) + { + listener.Handler(new Fdc3ChannelChangedEvent(channelId)); + } + }); }); } public Task LeaveCurrentChannel() { - return Task.Run(() => _currentChannel = null); + return Task.Run(() => { + _currentChannel = null; + + // Notify listeners of AddEventListener of leaving channel + _listeners.ForEach(listener => { + if (listener.EventType == Fdc3EventType.UserChannelChanged) + { + listener.Handler(new Fdc3ChannelChangedEvent(null)); + } + }); + }); } public Task Open(IAppIdentifier app, IContext? context = null) diff --git a/src/Examples/WpfFdc3/Fdc3/Listener.cs b/src/Examples/WpfFdc3/Fdc3/Listener.cs index 784013a..11888fc 100644 --- a/src/Examples/WpfFdc3/Fdc3/Listener.cs +++ b/src/Examples/WpfFdc3/Fdc3/Listener.cs @@ -18,6 +18,23 @@ namespace WpfFdc3.Fdc3 { + internal class EventListener : IListener + { + internal EventListener(string? eventType, Fdc3EventHandler handler) + { + this.EventType = eventType; + this.Handler = handler; + } + + public string? EventType { get; } + public Fdc3EventHandler Handler { get; } + + public void Unsubscribe() + { + throw new System.NotImplementedException(); + } + } + internal class Listener : IListener where T : IContext { private IEventAggregator _eventAggregator; diff --git a/src/Fdc3/Fdc3Event.cs b/src/Fdc3/Fdc3Event.cs new file mode 100644 index 0000000..a1436b2 --- /dev/null +++ b/src/Fdc3/Fdc3Event.cs @@ -0,0 +1,124 @@ +/* + * 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 System; + +namespace Finos.Fdc3 +{ + /// + /// Interface representing the format of event objects that may be received via the FDC3 API's AddEventListener function. + /// + public interface IFdc3Event + { + public string Type { get; } + public object? Details { get; } + } + + public delegate void Fdc3EventHandler(IFdc3Event fdc3Event); + + /// + /// Type representing the format of event objects that may be received via the FDC3 API's addEventListener function + /// + public class Fdc3Event : IFdc3Event + { + public string Type { get; } + public object? Details { get; } + + public Fdc3Event(string type, object? details = null) + { + this.Type = type ?? throw new ArgumentNullException(nameof(type)); + this.Details = details; + } + } + + /// + /// Valid type strings for DesktopAgent interface events. + /// + public static class Fdc3EventType + { + public const string UserChannelChanged = "userChannelChanged"; + } + + /// + /// Valid type strings for Private Channel events. + /// + public static class Fdc3PrivateChannelEventType + { + public const string AddContextListener = "addContextListener"; + public const string Unsubscribe = "unsubscribe"; + public const string Disconnect = "disconnect"; + } + + public interface IFdc3ChannelChangedEventDetails + { + string? CurrentChannelId { get; } + } + + public class Fdc3ChannelChangedEventDetails : IFdc3ChannelChangedEventDetails + { + public string? CurrentChannelId { get; } + + public Fdc3ChannelChangedEventDetails(string? channelId) + { + this.CurrentChannelId = channelId; + } + } + + public class Fdc3ChannelChangedEvent : Fdc3Event + { + public Fdc3ChannelChangedEvent(string? channelId) + : base(Fdc3EventType.UserChannelChanged, new Fdc3ChannelChangedEventDetails(channelId)) + { + } + } + + public interface IFdc3PrivateChannelEventDetails + { + string? ContextType { get; } + } + + public class Fdc3PrivateChannelEventDetails : IFdc3PrivateChannelEventDetails + { + public string? ContextType { get; } + + public Fdc3PrivateChannelEventDetails(string? contextType) + { + this.ContextType = contextType; + } + } + + public class Fdc3PrivateChannelAddContextListenerEvent : Fdc3Event + { + public Fdc3PrivateChannelAddContextListenerEvent(string? contextType) + : base(Fdc3PrivateChannelEventType.AddContextListener, new Fdc3PrivateChannelEventDetails(contextType)) + { + } + } + + public class Fdc3PrivateChannelUnsubscribeListenerEvent : Fdc3Event + { + public Fdc3PrivateChannelUnsubscribeListenerEvent(string? contextType) + : base(Fdc3PrivateChannelEventType.Unsubscribe, new Fdc3PrivateChannelEventDetails(contextType)) + { + } + } + + public class Fdc3PrivateChanneDisconnectEvent : Fdc3Event + { + public Fdc3PrivateChanneDisconnectEvent() + : base(Fdc3PrivateChannelEventType.Disconnect) + { + } + } +} diff --git a/src/Fdc3/IDesktopAgent.cs b/src/Fdc3/IDesktopAgent.cs index 686597e..aefe6dd 100644 --- a/src/Fdc3/IDesktopAgent.cs +++ b/src/Fdc3/IDesktopAgent.cs @@ -90,6 +90,12 @@ public interface IDesktopAgent /// Task AddContextListener(string? contextType, ContextHandler handler) where T : IContext; + /// + /// Register a handler for events from the DesktopAgent. Whenever the handler function is + /// called it will be passed an event object with details related to the event. + /// + Task AddEventListener(string? eventType, Fdc3EventHandler handler); + /// /// Retreives a list of the User channels available for the app to join. /// diff --git a/src/Fdc3/IPrivateChannel.cs b/src/Fdc3/IPrivateChannel.cs index 7368944..69a77ef 100644 --- a/src/Fdc3/IPrivateChannel.cs +++ b/src/Fdc3/IPrivateChannel.cs @@ -13,6 +13,7 @@ */ using System; +using System.Threading.Tasks; namespace Finos.Fdc3 { @@ -39,6 +40,7 @@ public interface IPrivateChannel : IChannel, IIntentResult /// channel, including those that occurred before this handler was registered /// (to prevent race conditions). /// + [Obsolete("Use AddEventListener")] IListener OnAddContextListener(Action handler); /// @@ -48,6 +50,7 @@ public interface IPrivateChannel : IChannel, IIntentResult /// Desktop Agents MUST call this when Disconnect() is called by the other party, for /// each listener that they had added. /// + [Obsolete("Use AddEventListener")] IListener OnUnsubscribe(Action handler); /// @@ -55,6 +58,7 @@ public interface IPrivateChannel : IChannel, IIntentResult /// when its window is closed or because disconnect was called.This is in addition /// to calls that will be made to OnUnsubscribe listeners. /// + [Obsolete("Use AddEventListener")] IListener OnDisconnect(Action handler); /// @@ -66,5 +70,12 @@ public interface IPrivateChannel : IChannel, IIntentResult /// before triggering any OnDisconnect handler added by the other party. /// void Disconnect(); + + + /// + /// Register a handler for events from the PrivateChannel. Whenever the handler function is + /// called it will be passed an event object with details related to the event. + /// + Task AddEventListener(string? eventType, Fdc3EventHandler handler); } } diff --git a/src/Tests/Finos.Fdc3.Tests/Fdc3EventTests.cs b/src/Tests/Finos.Fdc3.Tests/Fdc3EventTests.cs new file mode 100644 index 0000000..0b295a5 --- /dev/null +++ b/src/Tests/Finos.Fdc3.Tests/Fdc3EventTests.cs @@ -0,0 +1,92 @@ +/* + * 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 Finos.Fdc3.Tests; + +public class Fdc3EventTests +{ + [Fact] + public void Fdc3Event_PropertiesMatchParams() + { + IFdc3Event fdc3Event = new Fdc3Event("type", new Fdc3ChannelChangedEventDetails("channelid")); + Assert.Same("type", fdc3Event.Type); + Assert.IsType(fdc3Event.Details); + Assert.Same("channelid", ((Fdc3ChannelChangedEventDetails)fdc3Event.Details).CurrentChannelId); + + fdc3Event = new Fdc3Event("type", null); + Assert.Null(fdc3Event.Details); + } + + [Fact] + public void Fdc3ChannelChangedEventDetails_PropertiesMatchParams() + { + IFdc3ChannelChangedEventDetails details = new Fdc3ChannelChangedEventDetails("channelid"); + Assert.Same("channelid", details.CurrentChannelId); + + details = new Fdc3ChannelChangedEventDetails(null); + Assert.Null(details.CurrentChannelId); + } + + [Fact] + public void Fdc3ChannelChangedEvent_PropertiesMatchParams() + { + IFdc3Event fdc3Event = new Fdc3ChannelChangedEvent("channelid"); + Assert.Same(Fdc3EventType.UserChannelChanged, fdc3Event.Type); + Assert.IsType(fdc3Event.Details); + Assert.Same("channelid", ((Fdc3ChannelChangedEventDetails)fdc3Event.Details).CurrentChannelId); + + fdc3Event = new Fdc3ChannelChangedEvent(null); + Fdc3ChannelChangedEventDetails? details = fdc3Event.Details as Fdc3ChannelChangedEventDetails; + Assert.Null(details?.CurrentChannelId); + } + + [Fact] + public void Fdc3PrivateChannelEventDetails_PropertiesMatchParams() + { + IFdc3PrivateChannelEventDetails details = new Fdc3PrivateChannelEventDetails("contexttype"); + Assert.Same("contexttype", details.ContextType); + + details = new Fdc3PrivateChannelEventDetails(null); + Assert.Null(details.ContextType); + } + + [Fact] + public void Fdc3PrivateChannelAddContextListenerEvent_PropertiesMatchParams() + { + IFdc3Event fdc3Event = new Fdc3PrivateChannelAddContextListenerEvent("contextType"); + Assert.Same(Fdc3PrivateChannelEventType.AddContextListener, fdc3Event.Type); + + IFdc3PrivateChannelEventDetails? details = fdc3Event.Details as IFdc3PrivateChannelEventDetails; + Assert.Same("contextType", details?.ContextType); + + } + + [Fact] + public void Fdc3PrivateChannelUnsubscribeListenerEvent_PropertiesMatchParams() + { + IFdc3Event fdc3Event = new Fdc3PrivateChannelUnsubscribeListenerEvent("contextType"); + Assert.Same(Fdc3PrivateChannelEventType.Unsubscribe, fdc3Event.Type); + + IFdc3PrivateChannelEventDetails? details = fdc3Event.Details as IFdc3PrivateChannelEventDetails; + Assert.Same("contextType", details?.ContextType); + } + + [Fact] + public void Fdc3PrivateChanneDisconnectEvent_PropertiesMatchParams() + { + IFdc3Event fdc3Event = new Fdc3PrivateChanneDisconnectEvent(); + Assert.Same(Fdc3PrivateChannelEventType.Disconnect, fdc3Event.Type); + Assert.Null(fdc3Event.Details); + } +} \ No newline at end of file