using Remotely.Server.Enums; using Remotely.Server.Services; using Remotely.Shared.Helpers; namespace Remotely.Server.Models; /// /// Contains data related to a remote control session. Consuming projects /// are expected to derive a class from this and add project-specific properties. /// They should add these derived classes to /// when sessions are created. /// public class RemoteControlSession : IDisposable { private readonly ManualResetEventSlim _sessionReadySignal = new(false); private bool _disposedValue; private StreamerState _streamerState; public RemoteControlSession() { Created = DateTimeOffset.Now; } public string AccessKey { get; internal set; } = string.Empty; public string AgentConnectionId { get; set; } = string.Empty; public string AttendedSessionId { get; set; } = string.Empty; public DateTimeOffset Created { get; internal set; } public string DesktopConnectionId { get; internal set; } = string.Empty; public string DeviceId { get; set; } = string.Empty; public DateTimeOffset LastStateChange { get; private set; } = DateTimeOffset.Now; public string MachineName { get; internal set; } = string.Empty; public RemoteControlMode Mode { get; internal set; } /// /// Whether to notify the user via system notification when a remote /// control session has started. /// public bool NotifyUserOnStart { get; set; } = true; public string OrganizationId { get; set; } = string.Empty; public string OrganizationName { get; internal set; } = string.Empty; public string RelativeAccessUri => $"/Viewer?mode=Unattended&sessionId={UnattendedSessionId}&accessKey={AccessKey}&viewonly=False"; public string RequesterName { get; set; } = string.Empty; public string RequesterUserName { get; internal set; } = string.Empty; /// /// Whether the remote control session requires the end user's consent /// before the view can connect. /// public bool RequireConsent { get; set; } public DateTimeOffset StartTime { get; internal set; } /// /// Current state of the streamer (desktop process) that's associated /// with this session. /// public StreamerState StreamerState { get => _streamerState; set { _streamerState = value; LastStateChange = DateTimeOffset.Now; } } public Guid StreamId { get; internal set; } public Guid UnattendedSessionId { get; set; } public string UserConnectionId { get; set; } = string.Empty; /// /// Contains a collection of viewer SignalR connection IDs. /// public HashSet ViewerList { get; private set; } = new(); public bool ViewOnly { get; set; } /// /// Creates a new session based off this existing one, but with /// a new UnattendedSessionId and AccessKey. /// /// public RemoteControlSession CreateNew() { if (Mode != RemoteControlMode.Unattended) { throw new InvalidOperationException("Only available in unattended mode."); } var clone = (RemoteControlSession)MemberwiseClone(); clone.Created = DateTimeOffset.Now; clone.UnattendedSessionId = Guid.NewGuid(); clone.AccessKey = RandomGenerator.GenerateAccessKey(); clone.ViewerList = new(); clone.ViewOnly = false; return clone; } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } public Task WaitForSessionReady(TimeSpan waitTime) { return Task.Run(() => _sessionReadySignal.Wait(waitTime)); } public Task WaitForSessionReady(CancellationToken cancellationToken) { return Task.Run(() => { try { _sessionReadySignal.Wait(cancellationToken); return true; } catch { return false; } }); } internal void SetSessionReadyState(bool isReady) { if (isReady) { _sessionReadySignal.Set(); } else { _sessionReadySignal.Reset(); } } protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _sessionReadySignal.Dispose(); } _disposedValue = true; } } }