diff --git a/Agent.Installer.Win/Services/InstallerService.cs b/Agent.Installer.Win/Services/InstallerService.cs index fcb27c9e..7c3c1359 100644 --- a/Agent.Installer.Win/Services/InstallerService.cs +++ b/Agent.Installer.Win/Services/InstallerService.cs @@ -380,7 +380,6 @@ namespace Remotely.Agent.Installer.Win.Services Description = "Background service that maintains a connection to the Remotely server. The service is used for remote support and maintenance by this computer's administrators.", ServiceName = "Remotely_Service", StartType = ServiceStartMode.Automatic, - DelayedAutoStart = true, Parent = new ServiceProcessInstaller() }; diff --git a/Agent/Interfaces/IDeviceInformationService.cs b/Agent/Interfaces/IDeviceInformationService.cs index 1f043ea2..dfc4852f 100644 --- a/Agent/Interfaces/IDeviceInformationService.cs +++ b/Agent/Interfaces/IDeviceInformationService.cs @@ -15,6 +15,5 @@ namespace Remotely.Agent.Interfaces (double usedGB, double totalGB) GetMemoryInGB(); string GetAgentVersion(); List GetAllDrives(); - Task GetCpuUtilization(); } } diff --git a/Agent/Program.cs b/Agent/Program.cs index ecea3384..6c629730 100644 --- a/Agent/Program.cs +++ b/Agent/Program.cs @@ -9,7 +9,6 @@ using System; using System.Diagnostics; using System.IO; using System.ServiceProcess; -using System.Threading; using System.Threading.Tasks; using System.Runtime.Versioning; using Remotely.Agent.Services.Linux; @@ -55,6 +54,8 @@ namespace Remotely.Agent // TODO: All these should be registered as interfaces. serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddHostedService(services => services.GetRequiredService()); serviceCollection.AddScoped(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); diff --git a/Agent/Services/AgentHubConnection.cs b/Agent/Services/AgentHubConnection.cs index 585ccdb0..4d25bdc6 100644 --- a/Agent/Services/AgentHubConnection.cs +++ b/Agent/Services/AgentHubConnection.cs @@ -489,10 +489,7 @@ namespace Remotely.Agent.Services } }); - _hubConnection.On("TriggerHeartbeat", async () => - { - await SendHeartbeat().ConfigureAwait(false); - }); + _hubConnection.On("TriggerHeartbeat", SendHeartbeat); } private async Task VerifyServer() diff --git a/Agent/Services/CpuUtilizationSampler.cs b/Agent/Services/CpuUtilizationSampler.cs index a8979cf1..bace9bb7 100644 --- a/Agent/Services/CpuUtilizationSampler.cs +++ b/Agent/Services/CpuUtilizationSampler.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; @@ -9,24 +10,36 @@ using System.Threading.Tasks; namespace Remotely.Agent.Services; - -internal interface ICpuUtilizationSampler : IHostedService +public interface ICpuUtilizationSampler : IHostedService { double CurrentUtilization { get; } } internal class CpuUtilizationSampler : BackgroundService, ICpuUtilizationSampler { + private readonly ILogger _logger; private double _currentUtilization; + public CpuUtilizationSampler(ILogger logger) + { + _logger = logger; + } + public double CurrentUtilization => _currentUtilization; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { - var currentUtil = await GetCpuUtilization(stoppingToken); - Interlocked.Exchange(ref _currentUtilization, currentUtil); + try + { + var currentUtil = await GetCpuUtilization(stoppingToken); + Interlocked.Exchange(ref _currentUtilization, currentUtil); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while getting CPU utilization sample."); + } await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } } diff --git a/Agent/Services/DeviceInfoGeneratorBase.cs b/Agent/Services/DeviceInfoGeneratorBase.cs index 0330091d..bd54ccca 100644 --- a/Agent/Services/DeviceInfoGeneratorBase.cs +++ b/Agent/Services/DeviceInfoGeneratorBase.cs @@ -1,4 +1,5 @@ -using Remotely.Shared.Models; +using Microsoft.Extensions.Logging; +using Remotely.Shared.Models; using Remotely.Shared.Services; using Remotely.Shared.Utilities; using System; @@ -14,6 +15,13 @@ namespace Remotely.Agent.Services { public class DeviceInfoGeneratorBase { + protected readonly ILogger _logger; + + public DeviceInfoGeneratorBase(ILogger logger) + { + _logger = logger; + } + public Device GetDeviceBase(string deviceID, string orgID) { @@ -63,7 +71,7 @@ namespace Remotely.Agent.Services } catch (Exception ex) { - Logger.Write(ex, "Error getting system drive info."); + _logger.LogError(ex, "Error getting system drive info."); } return (0, 0); @@ -80,7 +88,7 @@ namespace Remotely.Agent.Services } catch (Exception ex) { - Logger.Write(ex, "Error getting agent version."); + _logger.LogError(ex, "Error getting agent version."); } return "0.0.0.0"; @@ -103,55 +111,9 @@ namespace Remotely.Agent.Services } catch (Exception ex) { - Logger.Write(ex, "Error getting drive info."); + _logger.LogError(ex, "Error getting drive info."); return null; } } - - public async Task GetCpuUtilization() - { - double totalUtilization = 0; - var utilizations = new Dictionary>(); - var processes = Process.GetProcesses(); - - foreach (var proc in processes) - { - try - { - var startTime = DateTimeOffset.Now; - var startCpuUsage = proc.TotalProcessorTime; - utilizations.Add(proc.Id, new Tuple(startTime, startCpuUsage)); - } - catch - { - continue; - } - } - - await Task.Delay(500); - - foreach (var kvp in utilizations) - { - var endTime = DateTimeOffset.Now; - try - { - var proc = Process.GetProcessById(kvp.Key); - var startTime = kvp.Value.Item1; - var startCpuUsage = kvp.Value.Item2; - var endCpuUsage = proc.TotalProcessorTime; - var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; - var totalMsPassed = (endTime - startTime).TotalMilliseconds; - var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); - totalUtilization += cpuUsageTotal; - } - catch - { - continue; - } - } - - return totalUtilization; - } - } } diff --git a/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs b/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs index 4d398436..8267e88b 100644 --- a/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs +++ b/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs @@ -1,4 +1,5 @@ -using Remotely.Agent.Interfaces; +using Microsoft.Extensions.Logging; +using Remotely.Agent.Interfaces; using Remotely.Shared.Models; using Remotely.Shared.Services; using Remotely.Shared.Utilities; @@ -13,12 +14,19 @@ namespace Remotely.Agent.Services.Linux public class DeviceInfoGeneratorLinux : DeviceInfoGeneratorBase, IDeviceInformationService { private readonly IProcessInvoker _processInvoker; + private readonly ICpuUtilizationSampler _cpuUtilSampler; - public DeviceInfoGeneratorLinux(IProcessInvoker processInvoker) + public DeviceInfoGeneratorLinux( + IProcessInvoker processInvoker, + ICpuUtilizationSampler cpuUtilSampler, + ILogger logger) + : base(logger) { _processInvoker = processInvoker; + _cpuUtilSampler = cpuUtilSampler; } - public async Task CreateDevice(string deviceId, string orgId) + + public Task CreateDevice(string deviceId, string orgId) { var device = GetDeviceBase(deviceId, orgId); @@ -34,15 +42,15 @@ namespace Remotely.Agent.Services.Linux device.TotalStorage = totalStorage; device.UsedMemory = usedMemory; device.TotalMemory = totalMemory; - device.CpuUtilization = await GetCpuUtilization(); + device.CpuUtilization = _cpuUtilSampler.CurrentUtilization; device.AgentVersion = GetAgentVersion(); } catch (Exception ex) { - Logger.Write(ex, "Error getting device info."); + _logger.LogError(ex, "Error getting device info."); } - return device; + return Task.FromResult(device); } private string GetCurrentUser() diff --git a/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs b/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs index a2e6adee..bfff735e 100644 --- a/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs +++ b/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using Remotely.Agent.Interfaces; using Remotely.Shared.Models; using Remotely.Shared.Services; @@ -15,7 +16,8 @@ namespace Remotely.Agent.Services.MacOS { private readonly IProcessInvoker _processInvoker; - public DeviceInfoGeneratorMac(IProcessInvoker processInvoker) + public DeviceInfoGeneratorMac(IProcessInvoker processInvoker, ILogger logger) + : base(logger) { _processInvoker = processInvoker; } @@ -40,40 +42,12 @@ namespace Remotely.Agent.Services.MacOS } catch (Exception ex) { - Logger.Write(ex, "Error getting device info."); + _logger.LogError(ex, "Error getting device info."); } return device; } - public new Task GetCpuUtilization() - { - try - { - var cpuPercentStrings = _processInvoker.InvokeProcessOutput("zsh", "-c \"ps -A -o %cpu\""); - - double cpuPercent = 0; - cpuPercentStrings - .Split(Environment.NewLine) - .ToList() - .ForEach(x => - { - if (double.TryParse(x, out var result)) - { - cpuPercent += result; - } - }); - - return Task.FromResult(cpuPercent / Environment.ProcessorCount / 100); - } - catch (Exception ex) - { - Logger.Write(ex, "Error while getting CPU utilization."); - } - - return Task.FromResult((double)0); - } - public (double usedGB, double totalGB) GetMemoryInGB() { try @@ -114,6 +88,33 @@ namespace Remotely.Agent.Services.MacOS } } + private Task GetCpuUtilization() + { + try + { + var cpuPercentStrings = _processInvoker.InvokeProcessOutput("zsh", "-c \"ps -A -o %cpu\""); + + double cpuPercent = 0; + cpuPercentStrings + .Split(Environment.NewLine) + .ToList() + .ForEach(x => + { + if (double.TryParse(x, out var result)) + { + cpuPercent += result; + } + }); + + return Task.FromResult(cpuPercent / Environment.ProcessorCount / 100); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while getting CPU utilization."); + } + + return Task.FromResult((double)0); + } private string GetCurrentUser() { var users = _processInvoker.InvokeProcessOutput("users", ""); diff --git a/Agent/Services/Windows/DeviceInfoGeneratorWin.cs b/Agent/Services/Windows/DeviceInfoGeneratorWin.cs index c11d96dd..762b68aa 100644 --- a/Agent/Services/Windows/DeviceInfoGeneratorWin.cs +++ b/Agent/Services/Windows/DeviceInfoGeneratorWin.cs @@ -1,4 +1,5 @@ -using Remotely.Agent.Interfaces; +using Microsoft.Extensions.Logging; +using Remotely.Agent.Interfaces; using Remotely.Shared.Models; using Remotely.Shared.Utilities; using Remotely.Shared.Win32; @@ -12,7 +13,17 @@ namespace Remotely.Agent.Services.Windows { public class DeviceInfoGeneratorWin : DeviceInfoGeneratorBase, IDeviceInformationService { - public async Task CreateDevice(string deviceId, string orgId) + private readonly ICpuUtilizationSampler _cpuUtilSampler; + + public DeviceInfoGeneratorWin( + ICpuUtilizationSampler cpuUtilSampler, + ILogger logger) + : base(logger) + { + _cpuUtilSampler = cpuUtilSampler; + } + + public Task CreateDevice(string deviceId, string orgId) { var device = GetDeviceBase(deviceId, orgId); @@ -28,15 +39,15 @@ namespace Remotely.Agent.Services.Windows device.TotalStorage = totalStorage; device.UsedMemory = usedMemory; device.TotalMemory = totalMemory; - device.CpuUtilization = await GetCpuUtilization(); + device.CpuUtilization = _cpuUtilSampler.CurrentUtilization; device.AgentVersion = GetAgentVersion(); } catch (Exception ex) { - Logger.Write(ex, "Error getting device info."); + _logger.LogError(ex, "Error getting device info."); } - return device; + return Task.FromResult(device); } public (double usedGB, double totalGB) GetMemoryInGB() diff --git a/Desktop.Linux/Program.cs b/Desktop.Linux/Program.cs index 91fe7af7..84b67e77 100644 --- a/Desktop.Linux/Program.cs +++ b/Desktop.Linux/Program.cs @@ -66,11 +66,12 @@ if (appState.ArgDict.TryGetValue("org-id", out var orgId)) var result = await provider.UseRemoteControlClient( args, "The remote control client for Remotely.", - serverUrl); + serverUrl, + false); if (!result.IsSuccess) { - logger.LogError(result.Exception, "Failed to remote control client."); + logger.LogError(result.Exception, "Failed to start remote control client."); Environment.Exit(1); } diff --git a/Desktop.Linux/Properties/PublishProfiles/packaged-linux-x64.pubxml b/Desktop.Linux/Properties/PublishProfiles/packaged-linux-x64.pubxml index 6e054f37..6e103b52 100644 --- a/Desktop.Linux/Properties/PublishProfiles/packaged-linux-x64.pubxml +++ b/Desktop.Linux/Properties/PublishProfiles/packaged-linux-x64.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Release x64 .net7.0 - ..\Agent\bin\Release\.net7.0\linux-x64\publish\Desktop + ..\Agent\bin\Release\net7.0\linux-x64\publish\Desktop linux-x64 true False diff --git a/Desktop.Win/Desktop.Win.csproj b/Desktop.Win/Desktop.Win.csproj index 595b2e84..752ab7a7 100644 --- a/Desktop.Win/Desktop.Win.csproj +++ b/Desktop.Win/Desktop.Win.csproj @@ -60,7 +60,7 @@ - + \ No newline at end of file diff --git a/Desktop.Win/Program.cs b/Desktop.Win/Program.cs index 9c821ec5..db92b98f 100644 --- a/Desktop.Win/Program.cs +++ b/Desktop.Win/Program.cs @@ -66,11 +66,12 @@ if (appState.ArgDict.TryGetValue("org-id", out var orgId)) var result = await provider.UseRemoteControlClient( args, "The remote control client for Remotely.", - serverUrl); + serverUrl, + false); if (!result.IsSuccess) { - logger.LogError(result.Exception, "Failed to remote control client."); + logger.LogError(result.Exception, "Failed to start remote control client."); Environment.Exit(1); } diff --git a/Desktop.Win/Properties/PublishProfiles/packaged-win-x64-debug.pubxml b/Desktop.Win/Properties/PublishProfiles/packaged-win-x64-debug.pubxml index ff6e08bf..d91a042e 100644 --- a/Desktop.Win/Properties/PublishProfiles/packaged-win-x64-debug.pubxml +++ b/Desktop.Win/Properties/PublishProfiles/packaged-win-x64-debug.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Debug x64 .net7.0-windows - ..\Agent\bin\Release\.net7.0\win10-x64\publish\Desktop + ..\Agent\bin\Release\net7.0\win10-x64\publish\Desktop true win10-x64 True diff --git a/Desktop.Win/Properties/PublishProfiles/packaged-win-x64.pubxml b/Desktop.Win/Properties/PublishProfiles/packaged-win-x64.pubxml index 86b0d964..afec5a03 100644 --- a/Desktop.Win/Properties/PublishProfiles/packaged-win-x64.pubxml +++ b/Desktop.Win/Properties/PublishProfiles/packaged-win-x64.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Release x64 .net7.0-windows - ..\Agent\bin\Release\.net7.0\win10-x64\publish\Desktop + ..\Agent\bin\Release\net7.0\win10-x64\publish\Desktop true win10-x64 False diff --git a/Desktop.Win/Properties/PublishProfiles/packaged-win-x86.pubxml b/Desktop.Win/Properties/PublishProfiles/packaged-win-x86.pubxml index 85142335..b0c4447f 100644 --- a/Desktop.Win/Properties/PublishProfiles/packaged-win-x86.pubxml +++ b/Desktop.Win/Properties/PublishProfiles/packaged-win-x86.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Release x86 .net7.0-windows - ..\Agent\bin\Release\.net7.0\win10-x86\publish\Desktop + ..\Agent\bin\Release\net7.0\win10-x86\publish\Desktop win10-x86 true False diff --git a/Server/API/LoginController.cs b/Server/API/LoginController.cs index e4653d98..07b8dec0 100644 --- a/Server/API/LoginController.cs +++ b/Server/API/LoginController.cs @@ -49,11 +49,14 @@ namespace Remotely.Server.API if (HttpContext?.User?.Identity?.IsAuthenticated == true) { orgId = _dataService.GetUserByNameWithOrg(HttpContext.User.Identity.Name)?.OrganizationID; - var activeSessions = _desktopSessionCache.Sessions.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name); - foreach (var session in activeSessions.ToList()) + var activeSessions = _desktopSessionCache + .Sessions + .Where(x => x.RequesterUserName == HttpContext.User.Identity.Name); + + foreach (var session in activeSessions) { - await _desktopHub.Clients.Client(session.Value.DesktopConnectionId).SendAsync("Disconnect", "User logged out."); - await _viewerHub.Clients.Clients(session.Value.ViewerList).SendAsync("ConnectionFailed"); + await _desktopHub.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out."); + await _viewerHub.Clients.Clients(session.ViewerList).SendAsync("ConnectionFailed"); } } await _signInManager.SignOutAsync(); diff --git a/Server/API/RemoteControlController.cs b/Server/API/RemoteControlController.cs index 0d10a3a9..ac17ad02 100644 --- a/Server/API/RemoteControlController.cs +++ b/Server/API/RemoteControlController.cs @@ -29,7 +29,7 @@ namespace Remotely.Server.API private readonly IServiceHubSessionCache _serviceSessionCache; private readonly IApplicationConfig _appConfig; private readonly IOtpProvider _otpProvider; - private readonly IHubEventHandlerEx _hubEvents; + private readonly IHubEventHandler _hubEvents; private readonly IDataService _dataService; private readonly SignInManager _signInManager; @@ -40,7 +40,7 @@ namespace Remotely.Server.API IHubContext serviceHub, IServiceHubSessionCache serviceSessionCache, IOtpProvider otpProvider, - IHubEventHandlerEx hubEvents, + IHubEventHandler hubEvents, IApplicationConfig appConfig) { _dataService = dataService; @@ -112,7 +112,7 @@ namespace Remotely.Server.API } - var sessionCount = _desktopSessionCache.Sessions.Values + var sessionCount = _desktopSessionCache.Sessions .OfType() .Count(x => x.OrganizationId == orgID); @@ -121,7 +121,7 @@ namespace Remotely.Server.API return BadRequest("There are already the maximum amount of active remote control sessions for your organization."); } - var sessionId = Guid.NewGuid().ToString(); + var sessionId = Guid.NewGuid(); var accessKey = RandomGenerator.GenerateAccessKey(); var session = new RemoteControlSessionEx() @@ -133,33 +133,32 @@ namespace Remotely.Server.API OrganizationId = orgID }; - _desktopSessionCache.Sessions.AddOrUpdate(sessionId, session, (k, v) => + _desktopSessionCache.AddOrUpdate($"{sessionId}", session, (k, v) => { if (v is RemoteControlSessionEx ex) { ex.ServiceConnectionId = HttpContext.Connection.Id; return ex; } + v.Dispose(); return session; }); var orgName = _dataService.GetOrganizationNameById(orgID); - Task CreateSessionFunc() - { - return _serviceHub.Clients.Client(serviceConnectionId).SendAsync("RemoteControl", - sessionId, - accessKey, - HttpContext.Connection.Id, - string.Empty, - orgName); - } + await _serviceHub.Clients.Client(serviceConnectionId).SendAsync("RemoteControl", + sessionId, + accessKey, + HttpContext.Connection.Id, + string.Empty, + orgName); - if (!await _hubEvents.TryWaitForSession(sessionId, CreateSessionFunc)) + var waitResult = await session.WaitForSessionReady(TimeSpan.FromSeconds(30)); + if (!waitResult) { return StatusCode(408, "The remote control process failed to start in time on the remote device."); } - + var otp = _otpProvider.GetOtp(targetDevice.ID); return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl/Viewer?mode=Unattended&sessionId={sessionId}&accessKey={accessKey}&otp={otp}"); diff --git a/Server/Areas/Identity/Pages/Account/Logout.cshtml b/Server/Areas/Identity/Pages/Account/Logout.cshtml index afecdc6a..bb02698a 100644 --- a/Server/Areas/Identity/Pages/Account/Logout.cshtml +++ b/Server/Areas/Identity/Pages/Account/Logout.cshtml @@ -16,11 +16,11 @@ { if (SignInManager.IsSignedIn(User)) { - var activeSessions = DesktopSessionCache.Sessions.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name); + var activeSessions = DesktopSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name); foreach (var session in activeSessions) { - await DesktopHubContext.Clients.Client(session.Value.DesktopConnectionId).SendAsync("Disconnect", "User logged out."); - await ViewerHubContext.Clients.Clients(session.Value.ViewerList).SendAsync("ConnectionFailed"); + await DesktopHubContext.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out."); + await ViewerHubContext.Clients.Clients(session.ViewerList).SendAsync("ConnectionFailed"); } await SignInManager.SignOutAsync(); diff --git a/Server/Components/Devices/DeviceCard.razor.cs b/Server/Components/Devices/DeviceCard.razor.cs index c3ff402d..5c5d7374 100644 --- a/Server/Components/Devices/DeviceCard.razor.cs +++ b/Server/Components/Devices/DeviceCard.razor.cs @@ -268,15 +268,34 @@ namespace Remotely.Server.Components.Devices AppState.InvokePropertyChanged(nameof(AppState.DevicesFrameChatSessions)); } - private void StartRemoteControl(bool viewOnly) + private async Task StartRemoteControl(bool viewOnly) { - if (!ServiceSessionCache.TryGetConnectionId(Device.ID, out var connectionId)) + if (!ServiceSessionCache.TryGetConnectionId(Device.ID, out _)) { ToastService.ShowToast("Device connection not found", classString: "bg-danger"); return; } - CircuitConnection.RemoteControl(Device.ID, viewOnly); + var result = await CircuitConnection.RemoteControl(Device.ID, viewOnly); + if (!result.IsSuccess) + { + return; + } + + var session = result.Value; + + if (!await session.WaitForSessionReady(TimeSpan.FromSeconds(20))) + { + ToastService.ShowToast("Session failed to start", classString: "bg-danger"); + return; + } + + JsInterop.OpenWindow( + $"/RemoteControl/Viewer" + + $"?mode=Unattended&sessionId={session.UnattendedSessionId}" + + $"&accessKey={session.AccessKey}" + + $"&viewonly={viewOnly}", + "_blank"); } private void ToggleIsSelected(ChangeEventArgs args) diff --git a/Server/Components/Devices/DevicesFrame.razor.cs b/Server/Components/Devices/DevicesFrame.razor.cs index f9415df6..32f77e20 100644 --- a/Server/Components/Devices/DevicesFrame.razor.cs +++ b/Server/Components/Devices/DevicesFrame.razor.cs @@ -174,16 +174,6 @@ namespace Remotely.Server.Components.Devices AddScriptResult(result); } break; - case CircuitEventName.UnattendedSessionReady: - { - var sessionId = (string)args.Params[0]; - var accessKey = (string)args.Params[1]; - var deviceId = (string)args.Params[2]; - var viewOnly = (bool)args.Params[3]; - - JsInterop.OpenWindow($"/RemoteControl/Viewer?mode=Unattended&sessionId={sessionId}&accessKey={accessKey}&viewonly={viewOnly}", "_blank"); - } - break; default: break; } diff --git a/Server/Hubs/CircuitConnection.cs b/Server/Hubs/CircuitConnection.cs index 1f4d8e66..e79202bb 100644 --- a/Server/Hubs/CircuitConnection.cs +++ b/Server/Hubs/CircuitConnection.cs @@ -1,5 +1,6 @@ using Immense.RemoteControl.Server.Abstractions; using Immense.RemoteControl.Server.Services; +using Immense.RemoteControl.Shared; using Immense.RemoteControl.Shared.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components.Authorization; @@ -40,7 +41,7 @@ namespace Remotely.Server.Hubs Task InvokeCircuitEvent(CircuitEventName eventName, params object[] args); Task ReinstallAgents(string[] deviceIDs); - Task RemoteControl(string deviceID, bool viewOnly); + Task> RemoteControl(string deviceID, bool viewOnly); Task RemoveDevices(string[] deviceIDs); @@ -206,7 +207,7 @@ namespace Remotely.Server.Hubs return Task.CompletedTask; } - public async Task RemoteControl(string deviceId, bool viewOnly) + public async Task> RemoteControl(string deviceId, bool viewOnly) { if (!_serviceSessionCache.TryGetByDeviceId(deviceId, out var targetDevice)) { @@ -214,66 +215,65 @@ namespace Remotely.Server.Hubs "The selected device is not online.", "Device is not online.", "bg-warning")); - return false; + return Result.Fail("Device is not online."); } - if (_dataService.DoesUserHaveAccessToDevice(deviceId, User)) - { - var sessionCount = _desktopSessionCache.Sessions.Values - .OfType() - .Count(x => x.OrganizationId == User.OrganizationID); - - if (sessionCount >= _appConfig.RemoteControlSessionLimit) - { - MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage, - "There are already the maximum amount of active remote control sessions for your organization.", - "Max number of concurrent sessions reached.", - "bg-warning")); - return false; - } - - if (!_serviceSessionCache.TryGetConnectionId(targetDevice.ID, out var serviceConnectionId)) - { - MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage, - "Service connection not found.", - "Service connection not found.", - "bg-warning")); - return false; - } - - var sessionId = Guid.NewGuid().ToString(); - var accessKey = RandomGenerator.GenerateAccessKey(); - - var session = new RemoteControlSessionEx() - { - UnattendedSessionId = sessionId, - UserConnectionId = ConnectionId, - ServiceConnectionId = serviceConnectionId, - DeviceId = deviceId, - ViewOnly = viewOnly, - OrganizationId = User.OrganizationID - }; - - _desktopSessionCache.Sessions.AddOrUpdate(sessionId, session, (k, v) => session); - - var organization = _dataService.GetOrganizationNameByUserName(User.UserName); - await _agentHubContext.Clients.Client(serviceConnectionId).SendAsync("RemoteControl", - sessionId, - accessKey, - ConnectionId, - User.UserOptions.DisplayName, - organization, - User.OrganizationID); - - return true; - } - else + if (!_dataService.DoesUserHaveAccessToDevice(deviceId, User)) { var device = _dataService.GetDevice(targetDevice.ID); _dataService.WriteEvent($"Remote control attempted by unauthorized user. Device ID: {deviceId}. User Name: {User.UserName}.", EventType.Warning, device?.OrganizationID); - return false; + return Result.Fail("Unauthorized."); + } + + var sessionCount = _desktopSessionCache.Sessions + .OfType() + .Count(x => x.OrganizationId == User.OrganizationID); + + if (sessionCount >= _appConfig.RemoteControlSessionLimit) + { + MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage, + "There are already the maximum amount of active remote control sessions for your organization.", + "Max number of concurrent sessions reached.", + "bg-warning")); + return Result.Fail("Max number of concurrent sessions reached."); + } + + if (!_serviceSessionCache.TryGetConnectionId(targetDevice.ID, out var serviceConnectionId)) + { + MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage, + "Service connection not found.", + "Service connection not found.", + "bg-warning")); + return Result.Fail("Service connection not found."); + } + + var sessionId = Guid.NewGuid(); + var accessKey = RandomGenerator.GenerateAccessKey(); + + var session = new RemoteControlSessionEx() + { + UnattendedSessionId = sessionId, + UserConnectionId = ConnectionId, + ServiceConnectionId = serviceConnectionId, + DeviceId = deviceId, + ViewOnly = viewOnly, + OrganizationId = User.OrganizationID + }; + + _desktopSessionCache.AddOrUpdate($"{sessionId}", session); + + var organization = _dataService.GetOrganizationNameByUserName(User.UserName); + await _agentHubContext.Clients.Client(serviceConnectionId).SendAsync("RemoteControl", + sessionId, + accessKey, + ConnectionId, + User.UserOptions.DisplayName, + organization, + User.OrganizationID); + + return Result.Ok(session); } public Task RemoveDevices(string[] deviceIDs) diff --git a/Server/Models/CircuitEventName.cs b/Server/Models/CircuitEventName.cs index dd8dfe0f..8cbe8379 100644 --- a/Server/Models/CircuitEventName.cs +++ b/Server/Models/CircuitEventName.cs @@ -8,7 +8,6 @@ namespace Remotely.Server.Models public enum CircuitEventName { DisplayMessage, - UnattendedSessionReady, ChatReceived, CommandResult, DeviceUpdate, diff --git a/Server/Program.cs b/Server/Program.cs index 791ab4b9..7647ab83 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -34,6 +34,7 @@ using Immense.RemoteControl.Server.Abstractions; using Microsoft.Extensions.DependencyInjection.Extensions; using Remotely.Shared.Services; using System; +using Immense.RemoteControl.Server.Services; var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; @@ -182,7 +183,6 @@ services.AddScoped(); services.AddScoped(); services.AddHostedService(); services.AddHostedService(); -services.AddHostedService(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); @@ -199,15 +199,12 @@ services.AddSingleton() services.AddRemoteControlServer(config => { - config.AddHubEventHandler(); + config.AddHubEventHandler(); config.AddViewerAuthorizer(); config.AddViewerHubDataProvider(); config.AddViewerPageDataProvider(); }); -services.RemoveAll(); -services.AddScoped(); -services.AddScoped(s => s.GetRequiredService()); services.AddSingleton(); var app = builder.Build(); @@ -255,13 +252,11 @@ app.UseCors("TrustedOriginPolicy"); app.UseRemoteControlServer(); -app.UseEndpoints(endpoints => -{ - endpoints.MapHub("/hubs/service"); - endpoints.MapControllers(); - endpoints.MapBlazorHub(); - endpoints.MapFallbackToPage("/_Host"); -}); + +app.MapHub("/hubs/service"); +app.MapControllers(); +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); using (var scope = app.Services.CreateScope()) { diff --git a/Server/Services/DesktopHubSessionCleanup.cs b/Server/Services/DesktopHubSessionCleanup.cs deleted file mode 100644 index ca85d4cb..00000000 --- a/Server/Services/DesktopHubSessionCleanup.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Immense.RemoteControl.Server.Services; -using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Timers; -using System.Linq; -using Timer = System.Timers.Timer; -using Immense.RemoteControl.Shared.Services; -using Microsoft.Build.Framework; -using Microsoft.Extensions.Logging; -using System.Text.Json; -using System.Collections.Generic; -using Immense.RemoteControl.Server.Models; - -namespace Remotely.Server.Services -{ - public class DesktopHubSessionCleanup : BackgroundService - { - private readonly IDesktopHubSessionCache _sessionCache; - private readonly ISystemTime _systemTime; - private readonly ILogger _logger; - private Task _cleanupTask; - private CancellationToken _stoppingToken; - - public DesktopHubSessionCleanup( - IDesktopHubSessionCache sessionCache, - ISystemTime systemTime, - ILogger logger) - { - _sessionCache = sessionCache; - _systemTime = systemTime; - _logger = logger; - } - - protected override Task ExecuteAsync(CancellationToken stoppingToken) - { - _stoppingToken = stoppingToken; - _cleanupTask = Task.Run(CleanupSessions, stoppingToken); - return Task.CompletedTask; - } - - private async Task CleanupSessions() - { - while (!_stoppingToken.IsCancellationRequested) - { - foreach (var session in _sessionCache.Sessions) - { - if (session.Value.Mode == RemoteControlMode.Unattended && - !session.Value.ViewerList.Any() && - session.Value.Created < _systemTime.Now.AddMinutes(-1)) - { - _logger.LogWarning("Removing expired session: {session}", JsonSerializer.Serialize(session.Value)); - _sessionCache.Sessions.Remove(session.Key, out _); - } - } - await Task.Delay(30_000, _stoppingToken); - } - } - } -} diff --git a/Server/Services/RcImplementations/HubEventHandler.cs b/Server/Services/RcImplementations/HubEventHandler.cs index efef011c..bc30ce98 100644 --- a/Server/Services/RcImplementations/HubEventHandler.cs +++ b/Server/Services/RcImplementations/HubEventHandler.cs @@ -17,25 +17,15 @@ using System.Threading.Tasks; namespace Remotely.Server.Services.RcImplementations { - public interface IHubEventHandlerEx : IHubEventHandler + public class HubEventHandler : IHubEventHandler { - Task TryWaitForSession(string sessionId, Func createSessionFunc); - } - - public class HubEventHandlerEx : IHubEventHandlerEx - { - private static readonly ConcurrentDictionary _sessionWaitHandlers = new(); - - private readonly ICircuitManager _circuitManager; private readonly IHubContext _serviceHub; - private readonly ILogger _logger; + private readonly ILogger _logger; - public HubEventHandlerEx( - ICircuitManager circuitManager, + public HubEventHandler( IHubContext serviceHub, - ILogger logger) + ILogger logger) { - _circuitManager = circuitManager; _serviceHub = serviceHub; _logger = logger; } @@ -108,29 +98,6 @@ namespace Remotely.Server.Services.RcImplementations return Task.CompletedTask; } - public Task NotifyUnattendedSessionReady(RemoteControlSession session, string relativeAccessUrl) - { - if (_sessionWaitHandlers.TryGetValue(session.UnattendedSessionId, out var waitHandle)) - { - waitHandle.Release(); - return Task.CompletedTask; - } - - if (session is not RemoteControlSessionEx ex) - { - _logger.LogError("Event should have been for RemoteControlSessionEx."); - return Task.CompletedTask; - } - - return _circuitManager.InvokeOnConnection( - ex.UserConnectionId, - CircuitEventName.UnattendedSessionReady, - session.UnattendedSessionId, - session.AccessKey, - ex.DeviceId, - ex.ViewOnly); - } - public Task RestartScreenCaster(RemoteControlSession session, HashSet viewerList) { @@ -151,29 +118,5 @@ namespace Remotely.Server.Services.RcImplementations ex.OrganizationName, ex.OrganizationId); } - - public async Task TryWaitForSession(string sessionId, Func createSessionFunc) - { - try - { - var waitHandle = _sessionWaitHandlers.AddOrUpdate(sessionId, new SemaphoreSlim(0, 1), (k, v) => - { - v.Release(); - return new SemaphoreSlim(0, 1); - }); - - await createSessionFunc(); - - return waitHandle.Wait(TimeSpan.FromSeconds(30)); - } - finally - { - if (_sessionWaitHandlers.TryRemove(sessionId, out var result)) - { - result.Dispose(); - } - } - } - } } diff --git a/Server/Services/RcImplementations/ViewerPageDataProvider.cs b/Server/Services/RcImplementations/ViewerPageDataProvider.cs index fe1dd004..049b5e98 100644 --- a/Server/Services/RcImplementations/ViewerPageDataProvider.cs +++ b/Server/Services/RcImplementations/ViewerPageDataProvider.cs @@ -12,31 +12,37 @@ namespace Remotely.Server.Services.RcImplementations { public class ViewerPageDataProvider : IViewerPageDataProvider { - private readonly IDataService _dataService; private readonly IApplicationConfig _appConfig; - + private readonly IDataService _dataService; public ViewerPageDataProvider(IDataService dataService, IApplicationConfig appConfig) { _dataService = dataService; _appConfig = appConfig; } - - public Task GetUserDisplayName(PageModel pageModel) + + public Task GetFaviconUrl(ViewerModel viewerModel) { - if (string.IsNullOrWhiteSpace(pageModel?.User?.Identity?.Name)) - { - return Task.FromResult(string.Empty); - } + return Task.FromResult("/_content/Immense.RemoteControl.Server/favicon.ico"); + } - var user = _dataService.GetUserByNameWithOrg(pageModel.User.Identity.Name); + public Task GetPageDescription(ViewerModel viewerModel) + { + return Task.FromResult("Open-source remote support tools."); + } - if (user is null) - { - return Task.FromResult(string.Empty); - } + public Task GetPageTitle(PageModel pageModel) + { + return Task.FromResult("Remotely Remote Control"); + } - var displayName = user.UserOptions?.DisplayName ?? user.UserName ?? string.Empty; - return Task.FromResult(displayName); + public Task GetProductName(PageModel pageModel) + { + return Task.FromResult("Remotely"); + } + + public Task GetProductSubtitle(PageModel pageModel) + { + return Task.FromResult("Remote Control"); } public Task GetTheme(PageModel pageModel) @@ -63,24 +69,22 @@ namespace Remotely.Server.Services.RcImplementations return Task.FromResult(appTheme); } - public Task GetPageTitle(PageModel pageModel) + public Task GetUserDisplayName(PageModel pageModel) { - return Task.FromResult("Remotely Remote Control"); - } + if (string.IsNullOrWhiteSpace(pageModel?.User?.Identity?.Name)) + { + return Task.FromResult(string.Empty); + } - public Task GetProductName(PageModel pageModel) - { - return Task.FromResult("Remotely"); - } + var user = _dataService.GetUserByNameWithOrg(pageModel.User.Identity.Name); - public Task GetProductSubtitle(PageModel pageModel) - { - return Task.FromResult("Remote Control"); - } + if (user is null) + { + return Task.FromResult(string.Empty); + } - public Task GetPageDescription(ViewerModel viewerModel) - { - return Task.FromResult("Open-source remote support tools."); + var displayName = user.UserOptions?.DisplayName ?? user.UserName ?? string.Empty; + return Task.FromResult(displayName); } } } diff --git a/Tests/LoadTester/LoadTester.csproj b/Tests/LoadTester/LoadTester.csproj index 24d910e5..26e7fe66 100644 --- a/Tests/LoadTester/LoadTester.csproj +++ b/Tests/LoadTester/LoadTester.csproj @@ -7,6 +7,10 @@ Remotely.Tests.LoadTester + + + + diff --git a/Tests/LoadTester/Program.cs b/Tests/LoadTester/Program.cs index 49bb899e..45832972 100644 --- a/Tests/LoadTester/Program.cs +++ b/Tests/LoadTester/Program.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.SignalR.Client; +using Castle.Core.Logging; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; +using Moq; using Remotely.Agent.Services; using Remotely.Agent.Services.Windows; using System; @@ -15,11 +18,17 @@ namespace Remotely.Tests.LoadTester private static int _agentCount; private static string _organizationId; private static string _serverurl; + private static Mock _cpuSampler; + private static Mock> _logger; private static DeviceInfoGeneratorWin _deviceInfo; private static void Main(string[] args) { - _deviceInfo = new DeviceInfoGeneratorWin(); + _cpuSampler = new Mock(); + _cpuSampler.Setup(x => x.CurrentUtilization).Returns(0); + _logger = new Mock>(); + + _deviceInfo = new DeviceInfoGeneratorWin(_cpuSampler.Object, _logger.Object); ConnectAgents(); Console.Write("Press Enter to exit..."); diff --git a/Utilities/Publish.ps1 b/Utilities/Publish.ps1 index 42604dc7..6a96cbe3 100644 --- a/Utilities/Publish.ps1 +++ b/Utilities/Publish.ps1 @@ -92,25 +92,25 @@ if ([string]::IsNullOrWhiteSpace($MSBuildPath) -or !(Test-Path -Path $MSBuildPat # Clear publish folders. -if ((Test-Path -Path "$Root\Agent\bin\Release\.net7.0\win10-x64\publish") -eq $true) { - Get-ChildItem -Path "$Root\Agent\bin\Release\.net7.0\win10-x64\publish" | Remove-Item -Force -Recurse +if ((Test-Path -Path "$Root\Agent\bin\Release\net7.0\win10-x64\publish") -eq $true) { + Get-ChildItem -Path "$Root\Agent\bin\Release\net7.0\win10-x64\publish" | Remove-Item -Force -Recurse } -if ((Test-Path -Path "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" ) -eq $true) { - Get-ChildItem -Path "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" | Remove-Item -Force -Recurse +if ((Test-Path -Path "$Root\Agent\bin\Release\net7.0\win10-x86\publish" ) -eq $true) { + Get-ChildItem -Path "$Root\Agent\bin\Release\net7.0\win10-x86\publish" | Remove-Item -Force -Recurse } -if ((Test-Path -Path "$Root\Agent\bin\Release\.net7.0\linux-x64\publish") -eq $true) { - Get-ChildItem -Path "$Root\Agent\bin\Release\.net7.0\linux-x64\publish" | Remove-Item -Force -Recurse +if ((Test-Path -Path "$Root\Agent\bin\Release\net7.0\linux-x64\publish") -eq $true) { + Get-ChildItem -Path "$Root\Agent\bin\Release\net7.0\linux-x64\publish" | Remove-Item -Force -Recurse } # Publish Core clients. -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\.net7.0\win10-x64\publish" "$Root\Agent" -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\.net7.0\linux-x64\publish" "$Root\Agent" -dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x86 --self-contained --configuration Release --output "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\net7.0\win10-x64\publish" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\net7.0\linux-x64\publish" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x86 --self-contained --configuration Release --output "$Root\Agent\bin\Release\net7.0\win10-x86\publish" "$Root\Agent" -New-Item -Path "$Root\Agent\bin\Release\.net7.0\win10-x64\publish\Desktop\" -ItemType Directory -Force -New-Item -Path "$Root\Agent\bin\Release\.net7.0\win10-x86\publish\Desktop\" -ItemType Directory -Force -New-Item -Path "$Root\Agent\bin\Release\.net7.0\linux-x64\publish\Desktop\" -ItemType Directory -Force +New-Item -Path "$Root\Agent\bin\Release\net7.0\win10-x64\publish\Desktop\" -ItemType Directory -Force +New-Item -Path "$Root\Agent\bin\Release\net7.0\win10-x86\publish\Desktop\" -ItemType Directory -Force +New-Item -Path "$Root\Agent\bin\Release\net7.0\linux-x64\publish\Desktop\" -ItemType Directory -Force # Publish Linux ScreenCaster @@ -155,7 +155,7 @@ if ($SignAssemblies) { } # Compress Core clients. -$PublishDir = "$Root\Agent\bin\Release\.net7.0\win10-x64\publish" +$PublishDir = "$Root\Agent\bin\Release\net7.0\win10-x64\publish" Compress-Archive -Path "$PublishDir\*" -DestinationPath "$PublishDir\Remotely-Win10-x64.zip" -Force while ((Test-Path -Path "$PublishDir\Remotely-Win10-x64.zip") -eq $false){ Write-Host "Waiting for archive to finish: $PublishDir\Remotely-Win10-x64.zip" @@ -163,7 +163,7 @@ while ((Test-Path -Path "$PublishDir\Remotely-Win10-x64.zip") -eq $false){ } Move-Item -Path "$PublishDir\Remotely-Win10-x64.zip" -Destination "$Root\Server\wwwroot\Content\Remotely-Win10-x64.zip" -Force -$PublishDir = "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" +$PublishDir = "$Root\Agent\bin\Release\net7.0\win10-x86\publish" Compress-Archive -Path "$PublishDir\*" -DestinationPath "$PublishDir\Remotely-Win10-x86.zip" -Force while ((Test-Path -Path "$PublishDir\Remotely-Win10-x86.zip") -eq $false){ Write-Host "Waiting for archive to finish: $PublishDir\Remotely-Win10-x86.zip" @@ -171,7 +171,7 @@ while ((Test-Path -Path "$PublishDir\Remotely-Win10-x86.zip") -eq $false){ } Move-Item -Path "$PublishDir\Remotely-Win10-x86.zip" -Destination "$Root\Server\wwwroot\Content\Remotely-Win10-x86.zip" -Force -$PublishDir = "$Root\Agent\bin\Release\.net7.0\linux-x64\publish" +$PublishDir = "$Root\Agent\bin\Release\net7.0\linux-x64\publish" Compress-Archive -Path "$PublishDir\*" -DestinationPath "$PublishDir\Remotely-Linux.zip" -Force while ((Test-Path -Path "$PublishDir\Remotely-Linux.zip") -eq $false){ Write-Host "Waiting for archive to finish: $PublishDir\Remotely-Win10-x86.zip" diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index c2f38702..39d4f853 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit c2f38702bbba6db17b73b5578b0173e1dd81ac39 +Subproject commit 39d4f853eaa2a87590fb3a56e35db244f6eae08e