Made circuit events strongly-typed via IMessenger.

This commit is contained in:
Jared Goodwin 2023-08-04 12:50:58 -07:00
parent 33d4384bec
commit c9aa682fda
33 changed files with 703 additions and 360 deletions

View File

@ -111,6 +111,10 @@ dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_field = false:silent
# IDE0130: Namespace does not match folder structure
dotnet_diagnostic.IDE0130.severity = error
[*.cs]
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
@ -167,3 +171,6 @@ dotnet_diagnostic.CS8600.severity = error
# CS8602: Dereference of a possibly null reference.
dotnet_diagnostic.CS8602.severity = error
# CS8631: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type.
dotnet_diagnostic.CS8631.severity = error

View File

@ -1,12 +1,12 @@
using Microsoft.AspNetCore.Components;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared.Enums;
using Remotely.Shared.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Remotely.Server.Components.Devices;
@ -30,10 +30,13 @@ public partial class ChatCard : AuthComponentBase, IDisposable
[Inject]
private IJsInterop JsInterop { get; init; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
public void Dispose()
{
AppState.PropertyChanged -= AppState_PropertyChanged;
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
Messenger.Unregister<ChatReceivedMessage, string>(this, CircuitConnection.ConnectionId);
GC.SuppressFinalize(this);
}
@ -46,7 +49,51 @@ public partial class ChatCard : AuthComponentBase, IDisposable
{
await base.OnInitializedAsync();
AppState.PropertyChanged += AppState_PropertyChanged;
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
await Messenger.Register<ChatReceivedMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleChatMessageReceived);
}
private async Task HandleChatMessageReceived(ChatReceivedMessage message)
{
if (message.DeviceId != Session.DeviceId)
{
return;
}
var session = AppState.DevicesFrameChatSessions.Find(x => x.DeviceId == message.DeviceId);
if (session is null)
{
return;
}
if (message.DidDisconnect)
{
session.ChatHistory.Add(new ChatHistoryItem()
{
Message = $"{Session.DeviceName} disconnected.",
Origin = ChatHistoryItemOrigin.System
});
}
else
{
session.ChatHistory.Add(new ChatHistoryItem()
{
Message = message.MessageText,
Origin = ChatHistoryItemOrigin.Device
});
}
if (!session.IsExpanded)
{
session.MissedChats++;
}
await InvokeAsync(StateHasChanged);
JsInterop.ScrollToEnd(_chatMessagesWindow);
}
private async void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -57,48 +104,6 @@ public partial class ChatCard : AuthComponentBase, IDisposable
}
}
private async void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
{
if (e.EventName == Models.CircuitEventName.ChatReceived)
{
var deviceId = (string)e.Params[0];
if (deviceId == Session.DeviceId)
{
var deviceName = (string)e.Params[1];
var message = (string)e.Params[2];
var disconnected = (bool)e.Params[3];
var session = AppState.DevicesFrameChatSessions.Find(x => x.DeviceId == deviceId);
if (disconnected)
{
session.ChatHistory.Add(new ChatHistoryItem()
{
Message = $"{Session.DeviceName} disconnected.",
Origin = ChatHistoryItemOrigin.System
});
}
else
{
session.ChatHistory.Add(new ChatHistoryItem()
{
Message = message,
Origin = ChatHistoryItemOrigin.Device
});
}
if (!session.IsExpanded)
{
session.MissedChats++;
}
await InvokeAsync(StateHasChanged);
JsInterop.ScrollToEnd(_chatMessagesWindow);
}
}
}
private void CloseChatCard()
{
AppState.DevicesFrameChatSessions.RemoveAll(x => x.DeviceId == Session.DeviceId);

View File

@ -1,7 +1,10 @@
using Microsoft.AspNetCore.Components;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
using Remotely.Shared.ViewModels;
using System;
@ -20,10 +23,13 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
[Inject]
private ICircuitConnection CircuitConnection { get; init; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
public void Dispose()
{
AppState.PropertyChanged -= AppState_PropertyChanged;
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
Messenger.Unregister<ChatReceivedMessage, string>(this, CircuitConnection.ConnectionId);
GC.SuppressFinalize(this);
}
@ -31,44 +37,36 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
{
await base.OnInitializedAsync();
AppState.PropertyChanged += AppState_PropertyChanged;
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
await Messenger.Register<ChatReceivedMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleChatMessageReceived);
}
private void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
private async Task HandleChatMessageReceived(ChatReceivedMessage message)
{
if (e.EventName == Models.CircuitEventName.ChatReceived)
if (AppState.DevicesFrameChatSessions.Exists(x => x.DeviceId == message.DeviceId) ||
message.DidDisconnect)
{
var deviceId = (string)e.Params[0];
if (!AppState.DevicesFrameChatSessions.Exists(x => x.DeviceId == deviceId))
{
var deviceName = (string)e.Params[1];
var message = (string)e.Params[2];
var disconnected = (bool)e.Params[3];
if (disconnected)
{
return;
}
var newChat = new ChatSession()
{
DeviceId = deviceId,
DeviceName = deviceName,
IsExpanded = true
};
newChat.ChatHistory.Add(new ChatHistoryItem()
{
Message = message,
Origin = ChatHistoryItemOrigin.Device
});
AppState.DevicesFrameChatSessions.Add(newChat);
InvokeAsync(StateHasChanged);
}
return;
}
var newChat = new ChatSession()
{
DeviceId = message.DeviceId,
DeviceName = message.DeviceName,
IsExpanded = true
};
newChat.ChatHistory.Add(new ChatHistoryItem()
{
Message = message.MessageText,
Origin = ChatHistoryItemOrigin.Device
});
AppState.DevicesFrameChatSessions.Add(newChat);
await InvokeAsync(StateHasChanged);
}
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)

View File

@ -1,13 +1,10 @@
using Immense.RemoteControl.Server.Abstractions;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Remotely.Server.Auth;
using Remotely.Server.Enums;
using Remotely.Server.Hubs;
using Remotely.Server.Models;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
@ -15,7 +12,6 @@ using Remotely.Shared.Utilities;
using Remotely.Shared.ViewModels;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
@ -69,10 +65,13 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
[Inject]
private IUpgradeService UpgradeService { get; init; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
public void Dispose()
{
AppState.PropertyChanged -= AppState_PropertyChanged;
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
Messenger.Unregister<DeviceStateChangedMessage, string>(this, CircuitConnection.ConnectionId);
GC.SuppressFinalize(this);
}
@ -84,7 +83,48 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
_currentVersion = UpgradeService.GetCurrentVersion();
_deviceGroups = DataService.GetDeviceGroups(UserName);
AppState.PropertyChanged += AppState_PropertyChanged;
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
await Messenger.Register<DeviceStateChangedMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleDeviceStateChanged);
}
private async Task HandleDeviceStateChanged(DeviceStateChangedMessage message)
{
if (message.Device.ID != Device.ID)
{
return;
}
// TODO: It would be cool to decorate user-editable properties
// with a "UserEditable" attribute, then use a source generator
// to create/update a method that copies property values for
// those that do not have the attribute. We could do the same
// with reflection, but this method is called too frequently,
// and the performance hit would likely be significant.
// If the card is expanded, only update the immutable UI
// elements, so any changes to the form fields aren't lost.
if (IsExpanded)
{
Device.CurrentUser = message.Device.CurrentUser;
Device.Platform = message.Device.Platform;
Device.TotalStorage = message.Device.TotalStorage;
Device.UsedStorage = message.Device.UsedStorage;
Device.Drives = message.Device.Drives;
Device.CpuUtilization = message.Device.CpuUtilization;
Device.TotalMemory = message.Device.TotalMemory;
Device.UsedMemory = message.Device.UsedMemory;
Device.AgentVersion = message.Device.AgentVersion;
Device.LastOnline = message.Device.LastOnline;
Device.PublicIP = message.Device.PublicIP;
Device.MacAddresses = message.Device.MacAddresses;
}
else
{
Device = message.Device;
}
await InvokeAsync(StateHasChanged);
}
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@ -97,25 +137,6 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
}
}
private async void CircuitConnection_MessageReceived(object? sender, CircuitEvent e)
{
switch (e.EventName)
{
case CircuitEventName.DeviceUpdate:
case CircuitEventName.DeviceWentOffline:
{
if (e.Params?.FirstOrDefault() is Device device &&
device.ID == Device?.ID)
{
Device = device;
await InvokeAsync(StateHasChanged);
}
break;
}
default:
break;
}
}
private void ContextMenuOpening(MouseEventArgs args)
{
if (GetCardState() == DeviceCardState.Normal)
@ -331,7 +352,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
await CircuitConnection.UninstallAgents(new[] { Device.ID });
AppState.DevicesFrameFocusedDevice = null;
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
ParentFrame.Refresh();
await ParentFrame.Refresh();
}
}

View File

@ -1,22 +1,22 @@
using Microsoft.AspNetCore.Authorization;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Remotely.Server.Enums;
using Remotely.Server.Hubs;
using Remotely.Server.Models;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared.Attributes;
using Remotely.Shared.Entities;
using Remotely.Shared.Utilities;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Remotely.Server.Components.Devices;
@ -29,7 +29,7 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
private readonly string _deviceGroupNone = Guid.NewGuid().ToString();
private readonly List<DeviceGroup> _deviceGroups = new();
private readonly List<Device> _devicesForPage = new();
private readonly object _devicesLock = new();
private readonly SemaphoreSlim _devicesLock = new(1,1);
private readonly List<Device> _filteredDevices = new();
private readonly List<PropertyInfo> _sortableProperties = new();
private int _currentPage = 1;
@ -50,24 +50,34 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
private IDataService DataService { get; init; } = null!;
[Inject]
private IJsInterop JsInterop { get; init; } = null!;
private IToastService ToastService { get; init; } = null!;
[Inject]
private IToastService ToastService { get; init; } = null!;
private IMessenger Messenger { get; init; } = null!;
private int TotalPages => (int)Math.Max(1, Math.Ceiling((decimal)_filteredDevices.Count / _devicesPerPage));
public void Dispose()
{
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
Messenger.Unregister<DisplayNotificationMessage, string>(this, CircuitConnection.ConnectionId);
Messenger.Unregister<ScriptResultMessage, string>(this, CircuitConnection.ConnectionId);
Messenger.Unregister<DeviceStateChangedMessage, string>(this, CircuitConnection.ConnectionId);
AppState.PropertyChanged -= AppState_PropertyChanged;
GC.SuppressFinalize(this);
}
public void Refresh()
private async Task HandleDisplayNotificationMessage(DisplayNotificationMessage message)
{
LoadDevices();
InvokeAsync(StateHasChanged);
AppState.AddTerminalLine(message.ConsoleText);
ToastService.ShowToast(message.ToastText, classString: message.ClassName);
await InvokeAsync(StateHasChanged);
}
public async Task Refresh()
{
await LoadDevices();
await InvokeAsync(StateHasChanged);
}
protected override async Task OnInitializedAsync()
@ -76,7 +86,21 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
EnsureUserSet();
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
await Messenger.Register<DisplayNotificationMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleDisplayNotificationMessage);
await Messenger.Register<DeviceStateChangedMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleDeviceStateChangedMessage);
await Messenger.Register<ScriptResultMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleScriptResultMessage);
AppState.PropertyChanged += AppState_PropertyChanged;
_deviceGroups.Clear();
@ -91,17 +115,43 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
_sortableProperties.AddRange(sortableProperties);
LoadDevices();
await LoadDevices();
}
protected override bool ShouldRender()
private async Task HandleScriptResultMessage(ScriptResultMessage message)
{
var shouldRender = base.ShouldRender();
if (shouldRender)
await AddScriptResult(message.ScriptResult);
}
private async Task HandleDeviceStateChangedMessage(DeviceStateChangedMessage message)
{
await _devicesLock.WaitAsync();
try
{
FilterDevices();
var device = message.Device;
foreach (var collection in new[] { _allDevices, _devicesForPage })
{
var index = collection.FindIndex(x => x.ID == device.ID);
if (index > -1)
{
collection[index] = device;
}
}
Debouncer.Debounce(TimeSpan.FromSeconds(2), Refresh);
}
return shouldRender;
finally
{
_devicesLock.Release();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
await FilterDevices();
}
private async Task AddScriptResult(ScriptResult result)
@ -138,65 +188,16 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
}
}
private async void CircuitConnection_MessageReceived(object? sender, CircuitEvent args)
{
switch (args.EventName)
{
case CircuitEventName.DeviceUpdate:
case CircuitEventName.DeviceWentOffline:
{
if (args.Params?.FirstOrDefault() is Device device)
{
lock (_devicesLock)
{
var index = _allDevices.FindIndex(x => x.ID == device.ID);
if (index > -1)
{
_allDevices[index] = device;
}
index = _devicesForPage.FindIndex(x => x.ID == device.ID);
if (index > -1)
{
_devicesForPage[index] = device;
}
}
Debouncer.Debounce(TimeSpan.FromSeconds(2), Refresh);
}
break;
}
case CircuitEventName.DisplayMessage:
{
var terminalMessage = (string)args.Params[0];
var toastMessage = (string)args.Params[1];
var className = (string)args.Params[2];
AppState.AddTerminalLine(terminalMessage);
ToastService.ShowToast(toastMessage, classString: className);
await InvokeAsync(StateHasChanged);
}
break;
case CircuitEventName.ScriptResult:
{
if (args.Params[0] is ScriptResult result)
{
await AddScriptResult(result);
}
}
break;
default:
break;
}
}
private void ClearSelectedCard()
{
AppState.DevicesFrameFocusedDevice = string.Empty;
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
}
private void FilterDevices()
private async Task FilterDevices()
{
lock (_devicesLock)
await _devicesLock.WaitAsync();
try
{
_filteredDevices.Clear();
var appendDevices = new List<Device>();
@ -262,8 +263,12 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
_devicesForPage.Clear();
_devicesForPage.AddRange(appendDevices.Concat(devicesForPage));
}
}
finally
{
_devicesLock.Release();
}
}
@ -277,17 +282,18 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
return $"oi-sort-{_sortDirection.ToString().ToLower()}";
}
private void HandleRefreshClicked()
private async Task HandleRefreshClicked()
{
Refresh();
await Refresh();
ToastService.ShowToast("Devices refreshed.");
}
private void LoadDevices()
private async Task LoadDevices()
{
EnsureUserSet();
lock (_devicesLock)
await _devicesLock.WaitAsync();
try
{
_allDevices.Clear();
@ -297,8 +303,12 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
_allDevices.AddRange(devices);
}
finally
{
_devicesLock.Release();
}
FilterDevices();
await FilterDevices();
}
private void PageDown()
{

View File

@ -1,10 +1,12 @@
using Immense.RemoteControl.Server.Abstractions;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Logging;
using Remotely.Server.Components.ModalContents;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
@ -38,6 +40,9 @@ public partial class Terminal : AuthComponentBase, IDisposable
[Inject]
private IDataService DataService { get; init; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
private string InputText
{
get => _inputText ?? string.Empty;
@ -90,7 +95,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
public void Dispose()
{
AppState.PropertyChanged -= AppState_PropertyChanged;
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
Messenger.Unregister<PowerShellCompletionsMessage, string>(this, CircuitConnection.ConnectionId);
GC.SuppressFinalize(this);
}
@ -106,9 +111,32 @@ public partial class Terminal : AuthComponentBase, IDisposable
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
await Messenger.Register<PowerShellCompletionsMessage, string>(
this,
CircuitConnection.ConnectionId,
HandlePowerShellCompletionsMessage);
AppState.PropertyChanged += AppState_PropertyChanged;
}
private async Task HandlePowerShellCompletionsMessage(PowerShellCompletionsMessage message)
{
var completion = message.Completion;
var intent = message.Intent;
switch (intent)
{
case CompletionIntent.ShowAll:
await DisplayCompletions(completion.CompletionMatches);
break;
case CompletionIntent.NextResult:
ApplyCompletion(completion);
break;
default:
break;
}
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
}
private void ApplyCompletion(PwshCommandCompletion completion)
{
try
@ -142,27 +170,6 @@ public partial class Terminal : AuthComponentBase, IDisposable
}
}
private async void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
{
if (e.EventName == Models.CircuitEventName.PowerShellCompletions)
{
var completion = (PwshCommandCompletion)e.Params[0];
var intent = (CompletionIntent)e.Params[1];
switch (intent)
{
case CompletionIntent.ShowAll:
await DisplayCompletions(completion.CompletionMatches);
break;
case CompletionIntent.NextResult:
ApplyCompletion(completion);
break;
default:
break;
}
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
}
}
private async Task DisplayCompletions(List<PwshCompletionResult> completionMatches)
{
var deviceId = AppState.DevicesFrameSelectedDevices.FirstOrDefault();

View File

@ -1,4 +1,4 @@
@using Nihs.SimpleMessenger;
@using Immense.SimpleMessenger;
@using Remotely.Server.Models.Messages;
@inject IMessenger Messenger

View File

@ -1,8 +1,10 @@
using Immense.RemoteControl.Server.Hubs;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Remotely.Server.Models;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared;
using Remotely.Shared.Dtos;
@ -24,6 +26,7 @@ public class AgentHub : Hub<IAgentHubClient>
private readonly ICircuitManager _circuitManager;
private readonly IDataService _dataService;
private readonly IExpiringTokenService _expiringTokenService;
private readonly IMessenger _messenger;
private readonly ILogger<AgentHub> _logger;
private readonly IAgentHubSessionCache _serviceSessionCache;
private readonly IHubContext<ViewerHub> _viewerHubContext;
@ -34,6 +37,7 @@ public class AgentHub : Hub<IAgentHubClient>
IHubContext<ViewerHub> viewerHubContext,
ICircuitManager circuitManager,
IExpiringTokenService expiringTokenService,
IMessenger messenger,
ILogger<AgentHub> logger)
{
_dataService = dataService;
@ -42,6 +46,7 @@ public class AgentHub : Hub<IAgentHubClient>
_appConfig = appConfig;
_circuitManager = circuitManager;
_expiringTokenService = expiringTokenService;
_messenger = messenger;
_logger = logger;
}
@ -65,20 +70,21 @@ public class AgentHub : Hub<IAgentHubClient>
}
}
public Task Chat(string message, bool disconnected, string browserConnectionId)
public async Task Chat(string messageText, bool disconnected, string browserConnectionId)
{
if (Device is null)
{
return Task.CompletedTask;
return;
}
if (_circuitManager.TryGetConnection(browserConnectionId, out var connection))
if (_circuitManager.TryGetConnection(browserConnectionId, out _))
{
return connection.InvokeCircuitEvent(CircuitEventName.ChatReceived, Device.ID, $"{Device.DeviceName}", message, disconnected);
var message = new ChatReceivedMessage(Device.ID, $"{Device.DeviceName}", messageText, disconnected);
await _messenger.Send(message, browserConnectionId);
}
else
{
return Clients.Caller.SendChatMessage(
await Clients.Caller.SendChatMessage(
senderName: string.Empty,
message: string.Empty,
orgName: string.Empty,
@ -152,7 +158,8 @@ public class AgentHub : Hub<IAgentHubClient>
foreach (var connection in connections)
{
await connection.InvokeCircuitEvent(CircuitEventName.DeviceUpdate, Device);
var message = new DeviceStateChangedMessage(Device);
await _messenger.Send(message, connection.ConnectionId);
}
return true;
}
@ -212,7 +219,8 @@ public class AgentHub : Hub<IAgentHubClient>
foreach (var connection in connections)
{
_ = connection.InvokeCircuitEvent(CircuitEventName.DeviceUpdate, Device);
var message = new DeviceStateChangedMessage(Device);
await _messenger.Send(message, connection.ConnectionId);
}
@ -220,19 +228,22 @@ public class AgentHub : Hub<IAgentHubClient>
}
public Task<bool> DisplayMessage(string consoleMessage, string popupMessage, string className, string requesterID)
public Task DisplayMessage(string consoleMessage, string popupMessage, string className, string requesterId)
{
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.DisplayMessage, consoleMessage, popupMessage, className);
var message = new DisplayNotificationMessage(consoleMessage, popupMessage, className);
return _messenger.Send(message, requesterId);
}
public Task<bool> DownloadFile(string fileID, string requesterID)
public Task DownloadFile(string fileID, string requesterId)
{
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.DownloadFile, fileID);
var message = new DownloadFileMessage(fileID);
return _messenger.Send(message, requesterId);
}
public Task<bool> DownloadFileProgress(int progressPercent, string requesterID)
public Task DownloadFileProgress(int progressPercent, string requesterId)
{
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.DownloadFileProgress, progressPercent);
var message = new DownloadFileProgressMessage(progressPercent);
return _messenger.Send(message, requesterId);
}
public string GetServerUrl()
@ -245,7 +256,7 @@ public class AgentHub : Hub<IAgentHubClient>
return $"{Device?.ServerVerificationToken}";
}
public override Task OnDisconnectedAsync(Exception? exception)
public override async Task OnDisconnectedAsync(Exception? exception)
{
try
{
@ -265,10 +276,11 @@ public class AgentHub : Hub<IAgentHubClient>
foreach (var connection in connections)
{
connection.InvokeCircuitEvent(CircuitEventName.DeviceWentOffline, Device);
var message = new DeviceStateChangedMessage(Device);
await _messenger.Send(message, connection.ConnectionId);
}
}
return base.OnDisconnectedAsync(exception);
await base.OnDisconnectedAsync(exception);
}
finally
{
@ -278,7 +290,8 @@ public class AgentHub : Hub<IAgentHubClient>
public Task ReturnPowerShellCompletions(PwshCommandCompletion completion, CompletionIntent intent, string senderConnectionId)
{
return _circuitManager.InvokeOnConnection(senderConnectionId, CircuitEventName.PowerShellCompletions, completion, intent);
var message = new PowerShellCompletionsMessage(completion, intent);
return _messenger.Send(message, senderConnectionId);
}
public async Task ScriptResult(string scriptResultId)
@ -289,9 +302,8 @@ public class AgentHub : Hub<IAgentHubClient>
return;
}
_ = await _circuitManager.InvokeOnConnection($"{result.Value.SenderConnectionID}",
CircuitEventName.ScriptResult,
result.Value);
var message = new ScriptResultMessage(result.Value);
await _messenger.Send(message, $"{result.Value.SenderConnectionID}");
}
public void ScriptResultViaApi(string commandID, string requestID)
@ -305,7 +317,8 @@ public class AgentHub : Hub<IAgentHubClient>
public Task SendLogs(string logChunk, string requesterConnectionId)
{
return _circuitManager.InvokeOnConnection(requesterConnectionId, CircuitEventName.RemoteLogsReceived, logChunk);
var message = new ReceiveLogsMessage(logChunk);
return _messenger.Send(message, requesterConnectionId);
}
public void SetServerVerificationToken(string verificationToken)
{
@ -316,9 +329,10 @@ public class AgentHub : Hub<IAgentHubClient>
Device.ServerVerificationToken = verificationToken;
_dataService.SetServerVerificationToken(Device.ID, verificationToken);
}
public Task TransferCompleted(string transferID, string requesterID)
public Task TransferCompleted(string transferId, string requesterId)
{
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.TransferCompleted, transferID);
var message = new TransferCompleteMessage(transferId);
return _messenger.Send(message, requesterId);
}
private async Task<bool> CheckForDeviceBan(params string[] deviceIdNameOrIPs)
{

View File

@ -1,15 +1,12 @@
using Immense.RemoteControl.Server.Abstractions;
using Immense.RemoteControl.Server.Services;
using Immense.RemoteControl.Server.Services;
using Immense.RemoteControl.Shared;
using Immense.RemoteControl.Shared.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Remotely.Server.Auth;
using Remotely.Server.Models;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared;
using Remotely.Shared.Entities;
@ -20,7 +17,6 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -28,7 +24,8 @@ namespace Remotely.Server.Hubs;
public interface ICircuitConnection
{
event EventHandler<CircuitEvent>? MessageReceived;
string ConnectionId { get; }
RemotelyUser User { get; }
Task DeleteRemoteLogs(string deviceId);
@ -39,7 +36,6 @@ public interface ICircuitConnection
Task GetRemoteLogs(string deviceId);
Task InvokeCircuitEvent(CircuitEventName eventName, params object[] args);
Task ReinstallAgents(string[] deviceIDs);
Task<Result<RemoteControlSessionEx>> RemoteControl(string deviceID, bool viewOnly);
@ -80,12 +76,11 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
private readonly ICircuitManager _circuitManager;
private readonly IDataService _dataService;
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
private readonly ConcurrentQueue<CircuitEvent> _eventQueue = new();
private readonly IExpiringTokenService _expiringTokenService;
private readonly ILogger<CircuitConnection> _logger;
private readonly IAgentHubSessionCache _agentSessionCache;
private readonly IMessenger _messenger;
private readonly IToastService _toastService;
private readonly ManualResetEventSlim _initSignal = new();
private RemotelyUser? _user;
public CircuitConnection(
@ -99,6 +94,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
IExpiringTokenService expiringTokenService,
IRemoteControlSessionCache remoteControlSessionCache,
IAgentHubSessionCache agentSessionCache,
IMessenger messenger,
ILogger<CircuitConnection> logger)
{
_dataService = dataService;
@ -111,33 +107,17 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
_expiringTokenService = expiringTokenService;
_remoteControlSessionCache = remoteControlSessionCache;
_agentSessionCache = agentSessionCache;
_messenger = messenger;
_logger = logger;
}
public event EventHandler<CircuitEvent>? MessageReceived;
public string ConnectionId { get; } = Guid.NewGuid().ToString();
public RemotelyUser User
{
get
{
if (_initSignal.Wait(TimeSpan.FromSeconds(5)) && _user is not null)
{
return _user;
}
_logger.LogError("Failed to resolve user.");
throw new InvalidOperationException("Failed to resolve user.");
}
internal set
{
_user = value;
if (_user is not null)
{
_initSignal.Set();
}
}
get => _user ?? throw new InvalidOperationException("User is not set.");
internal set => _user = value;
}
@ -212,12 +192,6 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
return _agentHubContext.Clients.Client(key).GetLogs(ConnectionId);
}
public Task InvokeCircuitEvent(CircuitEventName eventName, params object[] args)
{
_eventQueue.Enqueue(new CircuitEvent(eventName, args));
return Task.Run(ProcessMessages);
}
public override async Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
if (!string.IsNullOrWhiteSpace(ConnectionId))
@ -237,9 +211,8 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
_toastService.ShowToast2("Authorization failure.", Enums.ToastType.Error);
return;
}
_user = userResult.Value;
User = userResult.Value;
_circuitManager.TryAddConnection(ConnectionId, this);
_initSignal.Set();
}
await base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
@ -256,10 +229,13 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
{
if (!_agentSessionCache.TryGetByDeviceId(deviceId, out var targetDevice))
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
var message = new DisplayNotificationMessage(
"The selected device is not online.",
"Device is not online.",
"bg-warning"));
"bg-warning");
await _messenger.Send(message, ConnectionId);
return Result.Fail<RemoteControlSessionEx>("Device is not online.");
}
@ -281,19 +257,24 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
if (sessionCount >= _appConfig.RemoteControlSessionLimit)
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
var message = new DisplayNotificationMessage(
"There are already the maximum amount of active remote control sessions for your organization.",
"Max number of concurrent sessions reached.",
"bg-warning"));
"bg-warning");
await _messenger.Send(message, ConnectionId);
return Result.Fail<RemoteControlSessionEx>("Max number of concurrent sessions reached.");
}
if (!_agentSessionCache.TryGetConnectionId(targetDevice.ID, out var serviceConnectionId))
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
"Service connection not found.",
"Service connection not found.",
"bg-warning"));
var message = new DisplayNotificationMessage(
"Service connection not found.",
"Service connection not found.",
"bg-warning");
await _messenger.Send(message, ConnectionId);
return Result.Fail<RemoteControlSessionEx>("Service connection not found.");
}
@ -463,24 +444,30 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
_dataService.RemoveDevices(deviceIDs);
}
public Task UpdateTags(string deviceID, string tags)
public async Task UpdateTags(string deviceID, string tags)
{
if (_dataService.DoesUserHaveAccessToDevice(deviceID, User))
{
if (tags.Length > 200)
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
var message = new CircuitEvent(CircuitEventName.DisplayMessage,
$"Tag must be 200 characters or less. Supplied length is {tags.Length}.",
"Tag must be under 200 characters.",
"bg-warning"));
"bg-warning");
await _messenger.Send(message, ConnectionId);
return;
}
_dataService.UpdateTags(deviceID, tags);
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
await _dataService.UpdateTags(deviceID, tags);
var successMessage = new DisplayNotificationMessage(
"Device updated successfully.",
"Device updated.",
"bg-success"));
"bg-success");
await _messenger.Send(successMessage, ConnectionId);
}
return Task.CompletedTask;
}
public async Task<Result> WakeDevice(Device device)
@ -606,23 +593,6 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
}
}
private void ProcessMessages()
{
lock (_eventQueue)
{
while (_eventQueue.TryDequeue(out var circuitEvent))
{
try
{
MessageReceived?.Invoke(this, circuitEvent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while invoking circuit event.");
}
}
}
}
private async Task SendWakeCommand(Device deviceToWake, IEnumerable<Device> peerDevices)
{

View File

@ -0,0 +1,3 @@
namespace Remotely.Server.Models.Messages;
public record ChatReceivedMessage(string DeviceId, string DeviceName, string MessageText, bool DidDisconnect = false);

View File

@ -0,0 +1,5 @@
using Remotely.Shared.Entities;
namespace Remotely.Server.Models.Messages;
public record DeviceStateChangedMessage(Device Device);

View File

@ -0,0 +1,3 @@
namespace Remotely.Server.Models.Messages;
public record DisplayNotificationMessage(string ConsoleText, string ToastText, string ClassName);

View File

@ -0,0 +1,3 @@
namespace Remotely.Server.Models.Messages;
public record DownloadFileMessage(string MessageId);

View File

@ -0,0 +1,3 @@
namespace Remotely.Server.Models.Messages;
public record DownloadFileProgressMessage(int ProgressPercent);

View File

@ -0,0 +1,6 @@
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
namespace Remotely.Server.Models.Messages;
public record PowerShellCompletionsMessage(PwshCommandCompletion Completion, CompletionIntent Intent);

View File

@ -0,0 +1,3 @@
namespace Remotely.Server.Models.Messages;
public record ReceiveLogsMessage(string LogChunk);

View File

@ -0,0 +1,5 @@
using Remotely.Shared.Entities;
namespace Remotely.Server.Models.Messages;
public record ScriptResultMessage(ScriptResult ScriptResult);

View File

@ -1,13 +1,3 @@
namespace Remotely.Server.Models.Messages;
public class ShowLoaderMessage
{
public ShowLoaderMessage(bool isShown, string statusMessage)
{
IsShown = isShown;
StatusMessage = statusMessage;
}
public bool IsShown { get; }
public string StatusMessage { get; }
}
public record ShowLoaderMessage(bool IsShown, string StatusMessage);

View File

@ -0,0 +1,3 @@
namespace Remotely.Server.Models.Messages;
public record TransferCompleteMessage(string TransferId);

View File

@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Components;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
using Remotely.Server.Components;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
@ -52,6 +54,9 @@ public partial class DeviceDetails : AuthComponentBase
[Inject]
private IToastService ToastService { get; set; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
protected override async Task OnInitializedAsync()
{
@ -74,18 +79,18 @@ public partial class DeviceDetails : AuthComponentBase
}
_deviceGroups = DataService.GetDeviceGroups(UserName);
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
await Messenger.Register<ReceiveLogsMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleReceiveLogsMessage);
_isLoading = false;
}
private void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
private async Task HandleReceiveLogsMessage(ReceiveLogsMessage message)
{
if (e.EventName == Models.CircuitEventName.RemoteLogsReceived)
{
var logChunk = (string)e.Params[0];
_logLines.Enqueue(logChunk);
InvokeAsync(StateHasChanged);
}
_logLines.Enqueue(message.LogChunk);
await InvokeAsync(StateHasChanged);
}
private async Task DeleteLogs()

View File

@ -14,7 +14,6 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Microsoft.Extensions.Logging;
using Npgsql;
using Remotely.Server.Areas.Identity;
using Remotely.Server.Auth;
using Remotely.Server.Data;
@ -28,10 +27,10 @@ using Remotely.Server.Services.RcImplementations;
using Remotely.Shared.Services;
using System;
using Serilog;
using Nihs.SimpleMessenger;
using Microsoft.AspNetCore.RateLimiting;
using RatePolicyNames = Remotely.Server.RateLimiting.PolicyNames;
using Remotely.Shared.Entities;
using Immense.SimpleMessenger;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
@ -211,7 +210,7 @@ services.AddScoped<IScriptScheduleDispatcher, ScriptScheduleDispatcher>();
services.AddSingleton<IOtpProvider, OtpProvider>();
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
services.AddSingleton<ILogsManager, LogsManager>();
services.AddScoped<IMessenger>((services) => new WeakReferenceMessenger());
services.AddSingleton(WeakReferenceMessenger.Default);
services.AddRemoteControlServer(config =>
{

View File

@ -32,7 +32,6 @@
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.8" />
<PackageReference Include="Nihs.SimpleMessenger" Version="1.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />

View File

@ -15,7 +15,6 @@ public interface ICircuitManager
ICollection<ICircuitConnection> Connections { get; }
bool TryAddConnection(string id, ICircuitConnection connection);
bool TryRemoveConnection(string id, [NotNullWhen(true)] out ICircuitConnection? connection);
Task<bool> InvokeOnConnection(string id, CircuitEventName eventName, params object[] args);
bool TryGetConnection(string id, [NotNullWhen(true)] out ICircuitConnection? connection);
}
public class CircuitManager : ICircuitManager
@ -30,23 +29,6 @@ public class CircuitManager : ICircuitManager
public ICollection<ICircuitConnection> Connections => _connections.Values;
public Task<bool> InvokeOnConnection(string browserConnectionId, CircuitEventName eventName, params object[] args)
{
try
{
if (_connections.TryGetValue(browserConnectionId, out var result))
{
result.InvokeCircuitEvent(eventName, args);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while invoking circuit event.");
return Task.FromResult(false);
}
}
public bool TryAddConnection(string id, ICircuitConnection connection)
{

View File

@ -1,7 +1,6 @@
using Nihs.ConcurrentList;
using Remotely.Server.Enums;
using Remotely.Server.Enums;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
using Remotely.Shared.Primitives;
using Remotely.Shared.ViewModels;
using System.Collections.Concurrent;
using System.ComponentModel;

View File

@ -1,5 +1,5 @@
using Immense.RemoteControl.Shared.Primitives;
using Nihs.SimpleMessenger;
using Immense.SimpleMessenger;
using Remotely.Server.Models.Messages;
using System;
using System.Threading.Tasks;

View File

@ -1,6 +1,6 @@
using Nihs.ConcurrentList;
using Remotely.Server.Enums;
using Remotely.Server.Enums;
using Remotely.Server.Models;
using Remotely.Shared.Primitives;
using System;
using System.Timers;

View File

@ -0,0 +1,198 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Remotely.Shared.Primitives;
/// <summary>
/// A simple, lock-based implementation of a thread-safe List<T>.
/// Note that a copy is returned when enumerating the list.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConcurrentList<T> : IList<T>
{
private readonly List<T> _list = new();
private readonly object _lock = new();
public int Count
{
get
{
lock (_lock)
{
return _list.Count;
}
}
}
public bool IsReadOnly => false;
public T this[int index]
{
get
{
lock (_lock)
{
return _list[index];
}
}
set
{
lock (_lock)
{
_list[index] = value;
}
}
}
public void Add(T item)
{
lock (_lock)
{
_list.Add(item);
}
}
public void AddRange(IEnumerable<T> collection)
{
lock (_lock)
{
_list.AddRange(collection);
}
}
public void Clear()
{
lock (_lock)
{
_list.Clear();
}
}
public bool Contains(T item)
{
lock (_lock)
{
return _list.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex)
{
lock (_lock)
{
_list.CopyTo(array, arrayIndex);
}
}
public bool Exists(Predicate<T> predicate)
{
lock (_lock)
{
return _list.Exists(predicate);
}
}
public List<T> FindAll(Predicate<T> predicate)
{
lock (_lock)
{
return _list.FindAll(predicate);
}
}
public T? Find(Predicate<T> predicate)
{
lock (_lock)
{
return _list.Find(predicate);
}
}
public int FindIndex(Predicate<T> predicate)
{
lock (_lock)
{
return _list.FindIndex(predicate);
}
}
public T? FindLast(Predicate<T> predicate)
{
lock (_lock)
{
return _list.FindLast(predicate);
}
}
public int FindLastIndex(Predicate<T> predicate)
{
lock (_lock)
{
return _list.FindLastIndex(predicate);
}
}
public IEnumerator<T> GetEnumerator()
{
lock (_lock)
{
return _list.ToList().GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
lock (_lock)
{
return _list.ToList().GetEnumerator();
}
}
public int IndexOf(T item)
{
lock (_lock)
{
return _list.IndexOf(item);
}
}
public void Insert(int index, T item)
{
lock (_lock)
{
_list.Insert(index, item);
}
}
public bool Remove(T item)
{
lock (_lock)
{
return _list.Remove(item);
}
}
public void RemoveAll(Predicate<T> predicate)
{
lock (_lock)
{
_list.RemoveAll(predicate);
}
}
public void RemoveAt(int index)
{
lock (_lock)
{
_list.RemoveAt(index);
}
}
public void RemoveRange(int index, int count)
{
lock (_lock)
{
_list.RemoveRange(index, count);
}
}
}

View File

@ -11,7 +11,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="7.0.9" />
<PackageReference Include="Nihs.ConcurrentList" Version="1.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.3" />

View File

@ -26,4 +26,20 @@ public static class Debouncer
_timers.TryAdd(key, timer);
timer.Start();
}
public static void Debounce(TimeSpan wait, Func<Task> func, [CallerMemberName] string key = "")
{
if (_timers.TryRemove(key, out var timer))
{
timer.Stop();
timer.Dispose();
}
timer = new Timer(wait.TotalMilliseconds)
{
AutoReset = false
};
timer.Elapsed += (s, e) => func();
_timers.TryAdd(key, timer);
timer.Start();
}
}

View File

@ -1,4 +1,4 @@
using Nihs.ConcurrentList;
using Remotely.Shared.Primitives;
using System;
namespace Remotely.Shared.ViewModels;

View File

@ -13,8 +13,10 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Remotely.Shared.Interfaces;
using Immense.SimpleMessenger;
using Remotely.Tests;
namespace Remotely.Tests;
namespace Remotely.Server.Tests;
[TestClass]
public class AgentHubTests
@ -34,17 +36,19 @@ public class AgentHubTests
var viewerHub = new Mock<IHubContext<ViewerHub>>();
var expiringTokenService = new Mock<IExpiringTokenService>();
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
var messenger = new Mock<IMessenger>();
var logger = new Mock<ILogger<AgentHub>>();
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { $"{_testData.Org1Device1.DeviceName}" });
var hub = new AgentHub(
_dataService,
appConfig.Object,
serviceSessionCache.Object,
viewerHub.Object,
circuitManager.Object,
_dataService,
appConfig.Object,
serviceSessionCache.Object,
viewerHub.Object,
circuitManager.Object,
expiringTokenService.Object,
messenger.Object,
logger.Object);
var hubClients = new Mock<IHubCallerClients<IAgentHubClient>>();
@ -71,6 +75,7 @@ public class AgentHubTests
var viewerHub = new Mock<IHubContext<ViewerHub>>();
var expiringTokenService = new Mock<IExpiringTokenService>();
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
var messenger = new Mock<IMessenger>();
var logger = new Mock<ILogger<AgentHub>>();
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.ID });
@ -82,6 +87,7 @@ public class AgentHubTests
viewerHub.Object,
circuitManager.Object,
expiringTokenService.Object,
messenger.Object,
logger.Object);
var hubClients = new Mock<IHubCallerClients<IAgentHubClient>>();
@ -123,7 +129,7 @@ public class AgentHubTests
public override void Abort()
{
}
}
}

View File

@ -1,6 +1,7 @@
#nullable enable
using Castle.Core.Logging;
using Immense.RemoteControl.Server.Services;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -37,6 +38,7 @@ public class CircuitConnectionTests
private Mock<IToastService> _toastService;
private Mock<IExpiringTokenService> _expiringTokenService;
private Mock<IRemoteControlSessionCache> _remoteControlSessionCache;
private Mock<IMessenger> _messenger;
private Mock<IAgentHubSessionCache> _agentSessionCache;
private Mock<ILogger<CircuitConnection>> _logger;
private CircuitConnection _circuitConnection;
@ -57,6 +59,7 @@ public class CircuitConnectionTests
_toastService = new Mock<IToastService>();
_expiringTokenService = new Mock<IExpiringTokenService>();
_remoteControlSessionCache = new Mock<IRemoteControlSessionCache>();
_messenger = new Mock<IMessenger>();
_agentSessionCache = new Mock<IAgentHubSessionCache>();
_logger = new Mock<ILogger<CircuitConnection>>();
@ -71,6 +74,7 @@ public class CircuitConnectionTests
_expiringTokenService.Object,
_remoteControlSessionCache.Object,
_agentSessionCache.Object,
_messenger.Object,
_logger.Object);
}

View File

@ -0,0 +1,80 @@
using Remotely.Shared.Primitives;
namespace Remotely.Shared.Tests;
[TestClass]
public class ConcurrentListTests
{
private readonly int _startCount = 500_000;
private ConcurrentList<int> _list = new();
[TestInitialize]
public void Setup()
{
_list = new ConcurrentList<int>();
for (var i = 0; i < _startCount; i++)
{
_list.Add(i);
}
}
[TestMethod]
public void MultipleOperations_GivenMultipleThreads_Ok()
{
Assert.IsTrue(_list.Contains(500));
Assert.IsTrue(_list.Contains(100_002));
var reset1 = new ManualResetEvent(false);
var reset2 = new ManualResetEvent(false);
var exceptions = 0;
// Add and remove items from two separate background threads.
_ = Task.Run(() =>
{
for (var i = 0; i < 5_000; i++)
{
try
{
Assert.IsTrue(_list.Remove(500 + i));
_list.RemoveAt(100_000);
_list.Add(42);
_list.Insert(200_000, 100);
}
catch
{
Interlocked.Increment(ref exceptions);
}
}
reset1.Set();
});
_ = Task.Run(() =>
{
for (var i = 5_000; i < 10_000; i++)
{
try
{
Assert.IsTrue(_list.Remove(500 + i));
_list.RemoveAt(100_000);
_list.Add(42);
_list.Insert(200_000, 100);
}
catch
{
Interlocked.Increment(ref exceptions);
}
}
reset2.Set();
});
reset1.WaitOne();
reset2.WaitOne();
Assert.IsFalse(_list.Contains(500));
Assert.IsFalse(_list.Contains(100_002));
// We should have the original count with which we started.
Assert.AreEqual(_startCount, _list.Count);
Assert.AreEqual(0, exceptions);
}
}