diff --git a/RoslynBridge/RoslynBridgePackage.cs b/RoslynBridge/RoslynBridgePackage.cs index a0be444..ee1f300 100644 --- a/RoslynBridge/RoslynBridgePackage.cs +++ b/RoslynBridge/RoslynBridgePackage.cs @@ -18,6 +18,7 @@ namespace RoslynBridge { public const string PackageGuidString = "b2c3d4e5-f6a7-4b5c-9d8e-0f1a2b3c4d5e"; private BridgeServer? _bridgeServer; + private Services.RegistrationService? _registrationService; protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { @@ -29,6 +30,10 @@ namespace RoslynBridge _bridgeServer = new BridgeServer(this); await _bridgeServer.StartAsync(); + // Register with WebAPI + _registrationService = new Services.RegistrationService(this, _bridgeServer.Port); + await _registrationService.RegisterAsync(); + await base.InitializeAsync(cancellationToken, progress); } catch (Exception ex) @@ -43,6 +48,8 @@ namespace RoslynBridge { if (disposing) { + _registrationService?.UnregisterAsync().Wait(TimeSpan.FromSeconds(5)); + _registrationService?.Dispose(); _bridgeServer?.Dispose(); } base.Dispose(disposing); diff --git a/RoslynBridge/Services/RegistrationService.cs b/RoslynBridge/Services/RegistrationService.cs new file mode 100644 index 0000000..a806b51 --- /dev/null +++ b/RoslynBridge/Services/RegistrationService.cs @@ -0,0 +1,138 @@ +using System; +using System.Diagnostics; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell; + +namespace RoslynBridge.Services +{ + /// + /// Service for registering this VS instance with the WebAPI + /// + public class RegistrationService : IDisposable + { + private readonly string _webApiUrl; + private readonly HttpClient _httpClient; + private readonly int _port; + private readonly AsyncPackage _package; + private System.Threading.Timer? _heartbeatTimer; + private bool _isRegistered; + + public RegistrationService(AsyncPackage package, int port) + { + _package = package; + _port = port; + _webApiUrl = ConfigurationService.Instance.WebApiUrl; + _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; + } + + public async Task RegisterAsync() + { + try + { + var dte = await GetDTEAsync(); + var solutionPath = dte?.Solution?.FullName; + var solutionName = string.IsNullOrEmpty(solutionPath) + ? null + : System.IO.Path.GetFileNameWithoutExtension(solutionPath); + + var registrationData = new + { + port = _port, + processId = Process.GetCurrentProcess().Id, + solutionPath = string.IsNullOrEmpty(solutionPath) ? null : solutionPath, + solutionName = solutionName, + projects = new string[] { } // TODO: Get project names + }; + + var json = JsonSerializer.Serialize(registrationData); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await _httpClient.PostAsync($"{_webApiUrl}/api/instances/register", content); + + if (response.IsSuccessStatusCode) + { + _isRegistered = true; + Debug.WriteLine($"Successfully registered with WebAPI at {_webApiUrl}"); + + // Start heartbeat timer (configurable interval) + var heartbeatInterval = TimeSpan.FromSeconds(ConfigurationService.Instance.HeartbeatIntervalSeconds); + _heartbeatTimer = new System.Threading.Timer( + async _ => await SendHeartbeatAsync(), + null, + heartbeatInterval, + heartbeatInterval); + } + else + { + Debug.WriteLine($"Failed to register with WebAPI: {response.StatusCode}"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error registering with WebAPI: {ex.Message}"); + // Don't throw - registration is optional, VS extension should work standalone + } + } + + private async Task SendHeartbeatAsync() + { + if (!_isRegistered) + return; + + try + { + var processId = Process.GetCurrentProcess().Id; + var response = await _httpClient.PostAsync( + $"{_webApiUrl}/api/instances/heartbeat/{processId}", + null); + + if (!response.IsSuccessStatusCode) + { + Debug.WriteLine($"Heartbeat failed: {response.StatusCode}"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error sending heartbeat: {ex.Message}"); + } + } + + public async Task UnregisterAsync() + { + if (!_isRegistered) + return; + + try + { + var processId = Process.GetCurrentProcess().Id; + var response = await _httpClient.PostAsync( + $"{_webApiUrl}/api/instances/unregister/{processId}", + null); + + if (response.IsSuccessStatusCode) + { + Debug.WriteLine("Successfully unregistered from WebAPI"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error unregistering from WebAPI: {ex.Message}"); + } + } + + private async Task GetDTEAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return await _package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE; + } + + public void Dispose() + { + _heartbeatTimer?.Dispose(); + _httpClient?.Dispose(); + } + } +}