mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Merge branch 'scripting_from_api'
This commit is contained in:
commit
0b50826537
@ -40,7 +40,6 @@ namespace Remotely.Agent.Services
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "winps":
|
||||
if (OSUtils.IsWindows)
|
||||
{
|
||||
@ -99,6 +98,50 @@ namespace Remotely.Agent.Services
|
||||
await hubConnection.InvokeAsync("DisplayMessage", "There was an error executing the command. It has been logged on the client device.", "Error executing command.", senderConnectionID);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteCommandFromApi(string mode, string requestID, string command, string commandID, string senderUserName, HubConnection hubConnection)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (mode.ToLower())
|
||||
{
|
||||
case "pscore":
|
||||
var psCoreResult = PSCore.GetCurrent(senderUserName).WriteInput(command, commandID);
|
||||
await SendResultsViaAjax("PSCore", psCoreResult);
|
||||
break;
|
||||
|
||||
case "winps":
|
||||
if (OSUtils.IsWindows)
|
||||
{
|
||||
var result = WindowsPS.GetCurrent(senderUserName).WriteInput(command, commandID);
|
||||
await SendResultsViaAjax("WinPS", result);
|
||||
}
|
||||
break;
|
||||
case "cmd":
|
||||
if (OSUtils.IsWindows)
|
||||
{
|
||||
var result = CMD.GetCurrent(senderUserName).WriteInput(command, commandID);
|
||||
await SendResultsViaAjax("CMD", result);
|
||||
}
|
||||
break;
|
||||
case "bash":
|
||||
if (OSUtils.IsLinux)
|
||||
{
|
||||
var result = Bash.GetCurrent(senderUserName).WriteInput(command, commandID);
|
||||
await SendResultsViaAjax("Bash", result);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
await hubConnection.InvokeAsync("CommandResultViaApi", commandID, requestID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
private async Task SendResultsViaAjax(string resultType, object result)
|
||||
{
|
||||
var targetURL = ConfigService.GetConnectionInfo().Host + $"/API/Commands/{resultType}";
|
||||
|
||||
@ -114,6 +114,17 @@ namespace Remotely.Agent.Services
|
||||
|
||||
await CommandExecutor.ExecuteCommand(mode, command, commandID, senderConnectionID, HubConnection);
|
||||
}));
|
||||
HubConnection.On("ExecuteCommandFromApi", (async (string mode, string requestID, string command, string commandID, string senderUserName) =>
|
||||
{
|
||||
if (!IsServerVerified)
|
||||
{
|
||||
Logger.Write($"Command attempted before server was verified. Mode: {mode}. Command: {command}. Sender: {senderUserName}");
|
||||
Uninstaller.UninstallAgent();
|
||||
return;
|
||||
}
|
||||
|
||||
await CommandExecutor.ExecuteCommandFromApi(mode, requestID, command, commandID, senderUserName, HubConnection);
|
||||
}));
|
||||
HubConnection.On("TransferFiles", async (string transferID, List<string> fileIDs, string requesterID) =>
|
||||
{
|
||||
Logger.Write($"File transfer started by {requesterID}.");
|
||||
|
||||
@ -211,6 +211,16 @@ namespace Remotely.ScreenCast.Core.Communication
|
||||
}
|
||||
});
|
||||
|
||||
Connection.On("Disconnect", async (string reason) =>
|
||||
{
|
||||
Logger.Write($"Disconnecting caster socket. Reason: {reason}");
|
||||
foreach (var viewer in conductor.Viewers.Values.ToList())
|
||||
{
|
||||
await Connection.InvokeAsync("ViewerDisconnected", viewer.ViewerConnectionID);
|
||||
viewer.DisconnectRequested = true;
|
||||
}
|
||||
});
|
||||
|
||||
Connection.On("GetScreenCast", (string viewerID, string requesterName) =>
|
||||
{
|
||||
try
|
||||
@ -302,11 +312,11 @@ namespace Remotely.ScreenCast.Core.Communication
|
||||
|
||||
Connection.On("ViewerDisconnected", async (string viewerID) =>
|
||||
{
|
||||
await Connection.InvokeAsync("ViewerDisconnected", viewerID);
|
||||
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
|
||||
{
|
||||
viewer.DisconnectRequested = true;
|
||||
}
|
||||
await Connection.InvokeAsync("ViewerDisconnected", viewerID);
|
||||
conductor.InvokeViewerRemoved(viewerID);
|
||||
|
||||
});
|
||||
|
||||
@ -10,6 +10,7 @@ using Remotely.Shared.Models;
|
||||
using Remotely.Server.Data;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Services;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
@ -19,16 +20,24 @@ namespace Remotely.Server.API
|
||||
[ApiController]
|
||||
public class LoginController : ControllerBase
|
||||
{
|
||||
public LoginController(SignInManager<RemotelyUser> signInManager, DataService dataService, ApplicationConfig appConfig)
|
||||
public LoginController(SignInManager<RemotelyUser> signInManager,
|
||||
DataService dataService,
|
||||
ApplicationConfig appConfig,
|
||||
IHubContext<RCDeviceSocketHub> rcDeviceHub,
|
||||
IHubContext<RCBrowserSocketHub> rcBrowserHub)
|
||||
{
|
||||
SignInManager = signInManager;
|
||||
DataService = dataService;
|
||||
AppConfig = appConfig;
|
||||
RCDeviceHub = rcDeviceHub;
|
||||
RCBrowserHub = rcBrowserHub;
|
||||
}
|
||||
|
||||
private SignInManager<RemotelyUser> SignInManager { get; }
|
||||
private DataService DataService { get; }
|
||||
public ApplicationConfig AppConfig { get; }
|
||||
private IHubContext<RCDeviceSocketHub> RCDeviceHub { get; }
|
||||
private IHubContext<RCBrowserSocketHub> RCBrowserHub { get; }
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody]ApiLogin login)
|
||||
@ -68,6 +77,12 @@ namespace Remotely.Server.API
|
||||
if (HttpContext?.User?.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
orgId = DataService.GetUserByName(HttpContext.User.Identity.Name)?.OrganizationID;
|
||||
var activeSessions = RCDeviceSocketHub.SessionInfoList.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
|
||||
foreach (var session in activeSessions.ToList())
|
||||
{
|
||||
await RCDeviceHub.Clients.Client(session.Value.RCDeviceSocketID).SendAsync("Disconnect", "User logged out.");
|
||||
await RCBrowserHub.Clients.Client(session.Value.RequesterSocketID).SendAsync("ConnectionFailed");
|
||||
}
|
||||
}
|
||||
await SignInManager.SignOutAsync();
|
||||
DataService.WriteEvent($"API logout successful for {HttpContext?.User?.Identity?.Name}.", orgId);
|
||||
|
||||
@ -94,19 +94,19 @@ namespace Remotely.Server.API
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
while (!RCDeviceSocketHub.SessionInfoList.Values.Any(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCSocketID)) && stopWatch.Elapsed.TotalSeconds < 5)
|
||||
while (!RCDeviceSocketHub.SessionInfoList.Values.Any(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCDeviceSocketID)) && stopWatch.Elapsed.TotalSeconds < 5)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
if (!RCDeviceSocketHub.SessionInfoList.Values.Any(x => x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y => y.Key != x.RCSocketID)))
|
||||
if (!RCDeviceSocketHub.SessionInfoList.Values.Any(x => x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y => y.Key != x.RCDeviceSocketID)))
|
||||
{
|
||||
return StatusCode(408, "The remote control process failed to start in time on the remote device.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var rcSession = RCDeviceSocketHub.SessionInfoList.Values.FirstOrDefault(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCSocketID));
|
||||
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl?clientID={rcSession.RCSocketID}&serviceID={targetDevice.Key}&fromApi=true");
|
||||
var rcSession = RCDeviceSocketHub.SessionInfoList.Values.FirstOrDefault(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCDeviceSocketID));
|
||||
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl?clientID={rcSession.RCDeviceSocketID}&serviceID={targetDevice.Key}&fromApi=true");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
84
Server/API/ScriptingController.cs
Normal file
84
Server/API/ScriptingController.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Remotely.Shared.Helpers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.IO;
|
||||
|
||||
namespace Remotely.Server.API
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class ScriptingController : ControllerBase
|
||||
{
|
||||
public ScriptingController(DataService dataService,
|
||||
UserManager<RemotelyUser> userManager,
|
||||
IHubContext<DeviceSocketHub> deviceHub)
|
||||
{
|
||||
DataService = dataService;
|
||||
UserManager = userManager;
|
||||
DeviceHub = deviceHub;
|
||||
}
|
||||
|
||||
private DataService DataService { get; }
|
||||
private IHubContext<DeviceSocketHub> DeviceHub { get; }
|
||||
private UserManager<RemotelyUser> UserManager { get; }
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("[action]/{mode}/{deviceID}")]
|
||||
public async Task<ActionResult<CommandContext>> ExecuteCommand(string mode, string deviceID)
|
||||
{
|
||||
var command = string.Empty;
|
||||
using (var sr = new StreamReader(Request.Body))
|
||||
{
|
||||
command = await sr.ReadToEndAsync();
|
||||
}
|
||||
var username = Request.HttpContext.User.Identity.Name;
|
||||
var user = await UserManager.FindByNameAsync(username);
|
||||
if (!DataService.DoesUserHaveAccessToDevice(deviceID, user))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
|
||||
KeyValuePair<string, Device> connection = DeviceSocketHub.ServiceConnections.FirstOrDefault(x =>
|
||||
x.Value.OrganizationID == user.OrganizationID &&
|
||||
x.Value.ID == deviceID);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connection.Key))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var commandContext = new CommandContext()
|
||||
{
|
||||
CommandMode = "PSCore",
|
||||
CommandText = command,
|
||||
SenderConnectionID = string.Empty,
|
||||
SenderUserID = user.Id,
|
||||
TargetDeviceIDs = new string[] { deviceID },
|
||||
OrganizationID = user.OrganizationID
|
||||
};
|
||||
DataService.AddOrUpdateCommandContext(commandContext);
|
||||
var requestID = Guid.NewGuid().ToString();
|
||||
await DeviceHub.Clients.Client(connection.Key).SendAsync("ExecuteCommandFromApi", mode, requestID, command, commandContext.ID, username);
|
||||
var success = await TaskHelper.DelayUntil(() => DeviceSocketHub.ApiScriptResults.TryGetValue(requestID, out _), TimeSpan.FromSeconds(30));
|
||||
if (!success)
|
||||
{
|
||||
return commandContext;
|
||||
}
|
||||
DeviceSocketHub.ApiScriptResults.TryGetValue(requestID, out var commandID);
|
||||
DeviceSocketHub.ApiScriptResults.Remove(requestID);
|
||||
DataService.DetachEntity(commandContext);
|
||||
var result = DataService.GetCommandContext(commandID.ToString(), username);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Server/Areas/Identity/IdentityHostingStartup.cs
Normal file
22
Server/Areas/Identity/IdentityHostingStartup.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.UI;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Remotely.Server.Data;
|
||||
using Remotely.Shared.Models;
|
||||
|
||||
[assembly: HostingStartup(typeof(Remotely.Server.Areas.Identity.IdentityHostingStartup))]
|
||||
namespace Remotely.Server.Areas.Identity
|
||||
{
|
||||
public class IdentityHostingStartup : IHostingStartup
|
||||
{
|
||||
public void Configure(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureServices((context, services) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Server/Areas/Identity/Pages/Account/Logout.cshtml
Normal file
10
Server/Areas/Identity/Pages/Account/Logout.cshtml
Normal file
@ -0,0 +1,10 @@
|
||||
@page
|
||||
@model LogoutModel
|
||||
@{
|
||||
ViewData["Title"] = "Log out";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<p>You have successfully logged out of the application.</p>
|
||||
</header>
|
||||
63
Server/Areas/Identity/Pages/Account/Logout.cshtml.cs
Normal file
63
Server/Areas/Identity/Pages/Account/Logout.cshtml.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
|
||||
namespace Remotely.Server.Areas.Identity.Pages.Account
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class LogoutModel : PageModel
|
||||
{
|
||||
private readonly ILogger<LogoutModel> _logger;
|
||||
private readonly SignInManager<RemotelyUser> _signInManager;
|
||||
public LogoutModel(SignInManager<RemotelyUser> signInManager,
|
||||
ILogger<LogoutModel> logger,
|
||||
IHubContext<RCDeviceSocketHub> rcDeviceHub,
|
||||
IHubContext<RCBrowserSocketHub> rcBrowserHub)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
RCDeviceHub = rcDeviceHub;
|
||||
RCBrowserHub = rcBrowserHub;
|
||||
}
|
||||
|
||||
private IHubContext<RCDeviceSocketHub> RCDeviceHub { get; }
|
||||
private IHubContext<RCBrowserSocketHub> RCBrowserHub { get; }
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
||||
{
|
||||
if (HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
var activeSessions = RCDeviceSocketHub.SessionInfoList.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
|
||||
foreach (var session in activeSessions.ToList())
|
||||
{
|
||||
await RCDeviceHub.Clients.Client(session.Value.RCDeviceSocketID).SendAsync("Disconnect", "User logged out.");
|
||||
await RCBrowserHub.Clients.Client(session.Value.RequesterSocketID).SendAsync("ConnectionFailed");
|
||||
}
|
||||
}
|
||||
|
||||
await _signInManager.SignOutAsync();
|
||||
_logger.LogInformation("User logged out.");
|
||||
if (returnUrl != null)
|
||||
{
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,10 @@ namespace Remotely.Server.Models
|
||||
public string MachineName { get; set; }
|
||||
public RemoteControlMode Mode { get; set; }
|
||||
public string OrganizationID { get; set; }
|
||||
public string RCSocketID { get; set; }
|
||||
public string RCDeviceSocketID { get; set; }
|
||||
public string RequesterName { get; set; }
|
||||
public string RequesterSocketID { get; set; }
|
||||
public string RequesterUserName { get; set; }
|
||||
public string ServiceID { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
}
|
||||
|
||||
@ -102,8 +102,7 @@ namespace Remotely.Server.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
RemotelyUser = DataService.GetUserByID(Context.UserIdentifier);
|
||||
if (await IsConnectionValid() == false)
|
||||
|
||||
@ -227,6 +227,12 @@ namespace Remotely.Server.Services
|
||||
RemotelyContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void DetachEntity(object entity)
|
||||
{
|
||||
RemotelyContext.Entry(entity).State = EntityState.Detached;
|
||||
}
|
||||
|
||||
|
||||
public void DeviceDisconnected(string deviceID)
|
||||
{
|
||||
var device = RemotelyContext.Devices.Find(deviceID);
|
||||
|
||||
@ -9,13 +9,15 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Remotely.Server.Services
|
||||
{
|
||||
public class DeviceSocketHub : Hub
|
||||
{
|
||||
public DeviceSocketHub(DataService dataService,
|
||||
IHubContext<BrowserSocketHub> browserHub,
|
||||
public DeviceSocketHub(DataService dataService,
|
||||
IHubContext<BrowserSocketHub> browserHub,
|
||||
IHubContext<RCBrowserSocketHub> rcBrowserHub)
|
||||
{
|
||||
DataService = dataService;
|
||||
@ -23,7 +25,8 @@ namespace Remotely.Server.Services
|
||||
RCBrowserHub = rcBrowserHub;
|
||||
}
|
||||
|
||||
public static ConcurrentDictionary<string, Device> ServiceConnections { get; } = new ConcurrentDictionary<string, Device>();
|
||||
public static ConcurrentDictionary<string, Device> ServiceConnections { get; } = new ConcurrentDictionary<string, Device>();
|
||||
public static IMemoryCache ApiScriptResults { get; } = new MemoryCache(new MemoryCacheOptions());
|
||||
public IHubContext<RCBrowserSocketHub> RCBrowserHub { get; }
|
||||
private IHubContext<BrowserSocketHub> BrowserHub { get; }
|
||||
private DataService DataService { get; }
|
||||
@ -44,6 +47,7 @@ namespace Remotely.Server.Services
|
||||
var commandContext = DataService.GetCommandContext(commandID);
|
||||
return BrowserHub.Clients.Client(commandContext.SenderConnectionID).SendAsync("BashResultViaAjax", commandID, Device.ID);
|
||||
}
|
||||
|
||||
public Task Chat(string message, string senderConnectionID)
|
||||
{
|
||||
return BrowserHub.Clients.Client(senderConnectionID).SendAsync("Chat", Device.DeviceName, message);
|
||||
@ -55,7 +59,7 @@ namespace Remotely.Server.Services
|
||||
return BrowserHub.Clients.Client(commandContext.SenderConnectionID).SendAsync("CMDResultViaAjax", commandID, Device.ID);
|
||||
}
|
||||
|
||||
public Task CommandResult(GenericCommandResult result)
|
||||
public Task CommandResult(GenericCommandResult result)
|
||||
{
|
||||
result.DeviceID = Device.ID;
|
||||
var commandContext = DataService.GetCommandContext(result.CommandContextID);
|
||||
@ -64,6 +68,11 @@ namespace Remotely.Server.Services
|
||||
return BrowserHub.Clients.Client(commandContext.SenderConnectionID).SendAsync("CommandResult", result);
|
||||
}
|
||||
|
||||
public void CommandResultViaApi(string commandID, string requestID)
|
||||
{
|
||||
ApiScriptResults.Set(requestID, commandID, DateTimeOffset.Now.AddHours(1));
|
||||
}
|
||||
|
||||
public Task DeviceCameOnline(Device device)
|
||||
{
|
||||
try
|
||||
|
||||
@ -166,7 +166,7 @@ namespace Remotely.Server.Services
|
||||
return Clients.Caller.SendAsync("SessionIDNotFound");
|
||||
}
|
||||
|
||||
screenCasterID = RCDeviceSocketHub.SessionInfoList.First(x => x.Value.AttendedSessionID == screenCasterID).Value.RCSocketID;
|
||||
screenCasterID = RCDeviceSocketHub.SessionInfoList.First(x => x.Value.AttendedSessionID == screenCasterID).Value.RCDeviceSocketID;
|
||||
}
|
||||
|
||||
RCDeviceSocketHub.SessionInfoList.TryGetValue(screenCasterID, out var sessionInfo);
|
||||
@ -188,6 +188,8 @@ namespace Remotely.Server.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
sessionInfo.OrganizationID = orgId;
|
||||
sessionInfo.RequesterUserName = Context.User.Identity.Name;
|
||||
sessionInfo.RequesterSocketID = Context.ConnectionId;
|
||||
}
|
||||
|
||||
DataService.WriteEvent(new EventLog()
|
||||
|
||||
@ -121,7 +121,7 @@ namespace Remotely.Server.Services
|
||||
{
|
||||
SessionInfo = new RCSessionInfo()
|
||||
{
|
||||
RCSocketID = Context.ConnectionId,
|
||||
RCDeviceSocketID = Context.ConnectionId,
|
||||
StartTime = DateTime.Now
|
||||
};
|
||||
SessionInfoList.AddOrUpdate(Context.ConnectionId, SessionInfo, (id, si) => SessionInfo);
|
||||
|
||||
21
Shared/Helpers/TaskHelper.cs
Normal file
21
Shared/Helpers/TaskHelper.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Shared.Helpers
|
||||
{
|
||||
public static class TaskHelper
|
||||
{
|
||||
public static async Task<bool> DelayUntil(Func<bool> condition, TimeSpan timeout, int pollingMs = 10)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
while (!condition() && sw.Elapsed < timeout)
|
||||
{
|
||||
await Task.Delay(pollingMs);
|
||||
}
|
||||
return condition();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user