From a2774bef53ddf8b2b8a758148b0de01c3ac1e3dd Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Tue, 9 Apr 2024 22:35:55 +0200 Subject: [PATCH] [frontend] Move streaming connection into service --- .../Core/Services/StreamingService.cs | 130 +++++++++++++++++ Iceshrimp.Frontend/Pages/Streaming.razor | 14 +- Iceshrimp.Frontend/Pages/Streaming.razor.cs | 136 +++++------------- Iceshrimp.Frontend/Startup.cs | 1 + 4 files changed, 176 insertions(+), 105 deletions(-) create mode 100644 Iceshrimp.Frontend/Core/Services/StreamingService.cs diff --git a/Iceshrimp.Frontend/Core/Services/StreamingService.cs b/Iceshrimp.Frontend/Core/Services/StreamingService.cs new file mode 100644 index 00000000..a3a87069 --- /dev/null +++ b/Iceshrimp.Frontend/Core/Services/StreamingService.cs @@ -0,0 +1,130 @@ +using Iceshrimp.Frontend.Core.Schemas; +using Iceshrimp.Shared.HubSchemas; +using Iceshrimp.Shared.Schemas; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http.Connections.Client; +using Microsoft.AspNetCore.SignalR.Client; +using TypedSignalR.Client; + +namespace Iceshrimp.Frontend.Core.Services; + +using NoteEvent = (StreamingTimeline timeline, NoteResponse note); + +internal class StreamingService( + SessionService session, + NavigationManager navigation, + ILogger logger +) : IAsyncDisposable +{ + private HubConnection? _hubConnection; + private IStreamingHubServer? _hub; + + public event EventHandler? Message; + public event EventHandler? Notification; + public event EventHandler? NotePublished; + public event EventHandler? NoteUpdated; + public event EventHandler? OnConnectionChange; + + public async Task Connect(StoredUser? user = null) + { + if (_hubConnection != null) + { + await _hubConnection.DisposeAsync(); + _hubConnection = null; + } + + user ??= session.Current; + + if (user == null) + return; + + _hubConnection = new HubConnectionBuilder() + .WithUrl(navigation.ToAbsoluteUri("/hubs/streaming"), Auth) + .AddMessagePackProtocol() + .Build(); + + _hub = _hubConnection.CreateHubProxy(); + _hubConnection.Register(new StreamingHubClient(this)); + + try + { + await _hubConnection.StartAsync(); + await _hub.Subscribe(StreamingTimeline.Home); + } + catch (Exception e) + { + Message?.Invoke(this, $"System: Connection failed - {e.Message}"); + logger.LogError("Connection failed: {error}", e.Message); + } + + return; + + void Auth(HttpConnectionOptions options) + { + options.AccessTokenProvider = () => Task.FromResult(user.Token); + } + } + + public async Task Send(string userInput, string messageInput) + { + if (_hub is not null) + await _hub.SendMessage(userInput, messageInput); + } + + public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected; + + public async ValueTask DisposeAsync() + { + if (_hubConnection is not null) + { + await _hubConnection.DisposeAsync(); + } + } + + private class StreamingHubClient(StreamingService streaming) : IStreamingHubClient, IHubConnectionObserver + { + public Task ReceiveMessage(string user, string message) + { + var encodedMsg = $"{user}: {message}"; + streaming.Message?.Invoke(this, encodedMsg); + return Task.CompletedTask; + } + + public Task Notification(NotificationResponse notification) + { + streaming.Notification?.Invoke(this, notification); + return Task.CompletedTask; + } + + public Task NotePublished(List timelines, NoteResponse note) + { + foreach (var timeline in timelines) + streaming.NotePublished?.Invoke(this, (timeline, note)); + return Task.CompletedTask; + } + + public Task NoteUpdated(List timelines, NoteResponse note) + { + foreach (var timeline in timelines) + streaming.NoteUpdated?.Invoke(this, (timeline, note)); + return Task.CompletedTask; + } + + public Task OnClosed(Exception? exception) + { + streaming.OnConnectionChange?.Invoke(this, EventArgs.Empty); + return ReceiveMessage("System", "Connection closed."); + } + + public Task OnReconnected(string? connectionId) + { + streaming.OnConnectionChange?.Invoke(this, EventArgs.Empty); + return ReceiveMessage("System", "Reconnected."); + } + + public Task OnReconnecting(Exception? exception) + { + return ReceiveMessage("System", "Reconnecting..."); + } + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Pages/Streaming.razor b/Iceshrimp.Frontend/Pages/Streaming.razor index 7e96a9dc..4e5ee660 100644 --- a/Iceshrimp.Frontend/Pages/Streaming.razor +++ b/Iceshrimp.Frontend/Pages/Streaming.razor @@ -1,6 +1,8 @@ @page "/streaming" -@inject NavigationManager Navigation -@implements IAsyncDisposable + +@code { + // See Streaming.razor.cs +} Home @@ -16,7 +18,7 @@ - +
@@ -25,8 +27,4 @@ {
  • @message
  • } - - -@code { - // See Streaming.razor.cs -} \ No newline at end of file + \ No newline at end of file diff --git a/Iceshrimp.Frontend/Pages/Streaming.razor.cs b/Iceshrimp.Frontend/Pages/Streaming.razor.cs index 69fe8f52..9d697f9a 100644 --- a/Iceshrimp.Frontend/Pages/Streaming.razor.cs +++ b/Iceshrimp.Frontend/Pages/Streaming.razor.cs @@ -2,119 +2,61 @@ using Iceshrimp.Frontend.Core.Services; using Iceshrimp.Shared.HubSchemas; using Iceshrimp.Shared.Schemas; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Http.Connections.Client; -using Microsoft.AspNetCore.SignalR.Client; -using TypedSignalR.Client; namespace Iceshrimp.Frontend.Pages; -public partial class Streaming +public partial class Streaming : IAsyncDisposable { - [Inject] private SessionService? Session { get; set; } + [Inject] private StreamingService StreamingService { get; init; } = null!; + private readonly List _messages = []; - private readonly List _messages = []; - - private HubConnection? _hubConnection; - private IStreamingHubServer? _hub; - private string _userInput = ""; - private string _messageInput = ""; - - private class StreamingHubClient(Streaming page) : IStreamingHubClient, IHubConnectionObserver - { - public Task ReceiveMessage(string user, string message) - { - var encodedMsg = $"{user}: {message}"; - page._messages.Add(encodedMsg); - page.InvokeAsync(page.StateHasChanged); - return Task.CompletedTask; - } - - public Task Notification(NotificationResponse notification) - { - var encodedMsg = $"Notification: {notification.Id} ({notification.Type})"; - page._messages.Add(encodedMsg); - page.InvokeAsync(page.StateHasChanged); - return Task.CompletedTask; - } - - public Task NotePublished(List timelines, NoteResponse note) - { - var encodedMsg = $"Note: {note.Id}"; - page._messages.Add(encodedMsg); - page.InvokeAsync(page.StateHasChanged); - return Task.CompletedTask; - } - - public Task NoteUpdated(List timelines, NoteResponse note) - { - var encodedMsg = $"Note update: {note.Id}"; - page._messages.Add(encodedMsg); - page.InvokeAsync(page.StateHasChanged); - return Task.CompletedTask; - } - - public Task OnClosed(Exception? exception) - { - return ReceiveMessage("System", "Connection closed."); - } - - public Task OnReconnected(string? connectionId) - { - return ReceiveMessage("System", "Reconnected."); - } - - public Task OnReconnecting(Exception? exception) - { - return ReceiveMessage("System", "Reconnecting..."); - } - } + private string _userInput = ""; + private string _messageInput = ""; protected override async Task OnInitializedAsync() { - _hubConnection = new HubConnectionBuilder() - .WithUrl(Navigation.ToAbsoluteUri("/hubs/streaming"), Auth) - .AddMessagePackProtocol() - .Build(); + StreamingService.Message += OnMessage; + StreamingService.Notification += OnNotification; + StreamingService.NotePublished += OnNotePublished; + StreamingService.NoteUpdated += OnNoteUpdated; - // This must be in a .razor.cs file for the code generator to work correctly - _hub = _hubConnection.CreateHubProxy(); - - _hubConnection.Register(new StreamingHubClient(this)); - - try - { - await _hubConnection.StartAsync(); - await _hub.Subscribe(StreamingTimeline.Home); - } - catch (Exception e) - { - _messages.Add($"System: Connection failed - {e.Message}"); - await InvokeAsync(StateHasChanged); - } - - return; - - void Auth(HttpConnectionOptions options) - { - options.AccessTokenProvider = () => Task.FromResult(Session?.Current?.Token); - } + await StreamingService.Connect(); } - private async Task Send() + private async Task Send() => await StreamingService.Send(_userInput, _messageInput); + + private async void OnMessage(object? _, string message) { - if (_hub is not null) - { - await _hub.SendMessage(_userInput, _messageInput); - } + _messages.Add(message); + await InvokeAsync(StateHasChanged); } - private bool IsConnected => _hubConnection?.State == HubConnectionState.Connected; + private async void OnNotification(object? _, NotificationResponse notification) + { + _messages.Add($"Notification: {notification.Id} ({notification.Type})"); + await InvokeAsync(StateHasChanged); + } + + private async void OnNotePublished(object? _, (StreamingTimeline timeline, NoteResponse note) data) + { + _messages.Add($"Note: {data.note.Id} ({data.timeline.ToString()})"); + await InvokeAsync(StateHasChanged); + } + + private async void OnNoteUpdated(object? _, (StreamingTimeline timeline, NoteResponse note) data) + { + _messages.Add($"Note updated: {data.note.Id} ({data.timeline.ToString()})"); + await InvokeAsync(StateHasChanged); + } public async ValueTask DisposeAsync() { - if (_hubConnection is not null) - { - await _hubConnection.DisposeAsync(); - } + StreamingService.Message -= OnMessage; + StreamingService.Notification -= OnNotification; + StreamingService.NotePublished -= OnNotePublished; + StreamingService.NoteUpdated -= OnNoteUpdated; + + await StreamingService.DisposeAsync(); + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/Iceshrimp.Frontend/Startup.cs b/Iceshrimp.Frontend/Startup.cs index 6137d5ff..ce4fde42 100644 --- a/Iceshrimp.Frontend/Startup.cs +++ b/Iceshrimp.Frontend/Startup.cs @@ -13,6 +13,7 @@ builder.Services.AddSingleton(_ => new HttpClient { BaseAddress = new Uri(builde builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddAuthorizationCore(); builder.Services.AddCascadingAuthenticationState();