diff --git a/ChatTwo/Http/HostContext.cs b/ChatTwo/Http/HostContext.cs index 8d67517..ae8d023 100644 --- a/ChatTwo/Http/HostContext.cs +++ b/ChatTwo/Http/HostContext.cs @@ -5,23 +5,24 @@ namespace ChatTwo.Http; public class HostContext { - private readonly Plugin Plugin; + public readonly ServerCore Core; public bool IsActive; public bool IsStopping; - internal WebserverLite Host; - internal Processing Processing; - internal RouteController RouteController; + // Initialized at webserver start + public WebserverLite Host = null!; + public Processing Processing = null!; + public RouteController RouteController = null!; - internal readonly List EventConnections = []; + public readonly List EventConnections = []; - internal readonly CancellationTokenSource TokenSource = new(); - internal readonly string StaticDir = Path.Combine(Plugin.Interface.AssemblyLocation.DirectoryName!, "Http/Frontend/build"); + public readonly CancellationTokenSource TokenSource = new(); + public readonly string StaticDir = Path.Combine(Plugin.Interface.AssemblyLocation.DirectoryName!, "Http/Frontend/build"); - public HostContext(Plugin plugin) + public HostContext(ServerCore core) { - Plugin = plugin; + Core = core; } public bool Start() @@ -30,8 +31,8 @@ public class HostContext { Host = new WebserverLite(new WebserverSettings("*", Plugin.Config.WebinterfacePort), DefaultRoute); - Processing = new Processing(Plugin); - RouteController = new RouteController(Plugin, this); + Processing = new Processing(this); + RouteController = new RouteController(this); Host.Routes.PreAuthentication.Content.BaseDirectory = StaticDir; Host.Routes.AuthenticateRequest = CheckAuthenticationCookie; @@ -82,7 +83,7 @@ public class HostContext Host.Stop(); // Save our session tokens - Plugin.SaveConfig(); + Core.Plugin.SaveConfig(); // We get a copy, so that the original can be cleaned up successfully foreach (var eventServer in EventConnections.ToArray()) diff --git a/ChatTwo/Http/Processing.cs b/ChatTwo/Http/Processing.cs index 169bd7a..8e18c1f 100644 --- a/ChatTwo/Http/Processing.cs +++ b/ChatTwo/Http/Processing.cs @@ -8,22 +8,22 @@ namespace ChatTwo.Http; public class Processing { - private readonly Plugin Plugin; + private readonly HostContext HostContext; - public Processing(Plugin plugin) + public Processing(HostContext hostContext) { - Plugin = plugin; + HostContext = hostContext; } internal (MessageTemplate[] Name, bool Locked) ReadChannelName(Chunk[] channelName) { - var locked = Plugin.CurrentTab is not { Channel: null }; + var locked = HostContext.Core.Plugin.CurrentTab is not { Channel: null }; return (channelName.Select(ProcessChunk).ToArray(), locked); } internal async Task ReadMessageList() { - var tabMessages = await Plugin.CurrentTab.Messages.GetCopy(); + var tabMessages = await HostContext.Core.Plugin.CurrentTab.Messages.GetCopy(); return tabMessages.TakeLast(Plugin.Config.WebinterfaceMaxLinesToSend).Select(ReadMessageContent).ToArray(); } @@ -41,24 +41,6 @@ public class Processing return response; } - internal async Task PrepareNewClient(SSEConnection sse) - { - // This takes long, so keep it outside the next frame - var messages = await GetAllMessages(); - - // Using the bulk message event to clear everything on the client side that may still exist - await Plugin.Framework.RunOnTick(() => - { - sse.OutboundQueue.Enqueue(new BulkMessagesEvent(messages)); - - sse.OutboundQueue.Enqueue(new SwitchChannelEvent(GetCurrentChannel())); - sse.OutboundQueue.Enqueue(new ChannelListEvent(GetValidChannels())); - - sse.OutboundQueue.Enqueue(new ChatTabSwitchedEvent(GetCurrentTab())); - sse.OutboundQueue.Enqueue(new ChatTabListEvent(GetAllTabs())); - }); - } - private MessageTemplate ProcessChunk(Chunk chunk) { if (chunk is IconChunk { } icon) @@ -87,12 +69,12 @@ public class Processing color ??= 0; var userContent = text.Content ?? string.Empty; - if (Plugin.ChatLogWindow.ScreenshotMode) + if (HostContext.Core.Plugin.ChatLogWindow.ScreenshotMode) { if (chunk.Link is PlayerPayload playerPayload) - userContent = Plugin.ChatLogWindow.HidePlayerInString(userContent, playerPayload.PlayerName, playerPayload.World.RowId); + userContent = HostContext.Core.Plugin.ChatLogWindow.HidePlayerInString(userContent, playerPayload.PlayerName, playerPayload.World.RowId); else if (Plugin.ClientState.LocalPlayer is { } player) - userContent = Plugin.ChatLogWindow.HidePlayerInString(userContent, player.Name.TextValue, player.HomeWorld.RowId); + userContent = HostContext.Core.Plugin.ChatLogWindow.HidePlayerInString(userContent, player.Name.TextValue, player.HomeWorld.RowId); } var isNotUrl = text.Link is not UriPayload; @@ -102,30 +84,30 @@ public class Processing return MessageTemplate.Empty; } - private async Task GetAllMessages() + public async Task GetAllMessages() { var messages = await WebserverUtil.FrameworkWrapper(ReadMessageList); return new Messages(messages); } - private SwitchChannel GetCurrentChannel() + public SwitchChannel GetCurrentChannel() { - var channel = ReadChannelName(Plugin.ChatLogWindow.PreviousChannel); + var channel = ReadChannelName(HostContext.Core.Plugin.ChatLogWindow.PreviousChannel); return new SwitchChannel(channel); } - private ChannelList GetValidChannels() + public ChannelList GetValidChannels() { - var channels = Plugin.ChatLogWindow.GetValidChannels(); + var channels = HostContext.Core.Plugin.ChatLogWindow.GetValidChannels(); return new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value)); } - private ChatTab GetCurrentTab() + public ChatTab GetCurrentTab() { - return new ChatTab(Plugin.CurrentTab.Name, Plugin.LastTab); + return new ChatTab(HostContext.Core.Plugin.CurrentTab.Name, HostContext.Core.Plugin.LastTab); } - private ChatTabList GetAllTabs() + public ChatTabList GetAllTabs() { var tabs = Plugin.Config.Tabs.Select((tab, idx) => new ChatTab(tab.Name, idx)).ToArray(); return new ChatTabList(tabs); diff --git a/ChatTwo/Http/RouteController.cs b/ChatTwo/Http/RouteController.cs index e593b92..50064b5 100644 --- a/ChatTwo/Http/RouteController.cs +++ b/ChatTwo/Http/RouteController.cs @@ -12,8 +12,7 @@ namespace ChatTwo.Http; public class RouteController { - private readonly Plugin Plugin; - private readonly HostContext Core; + private readonly HostContext HostContext; private readonly string AuthTemplate; private readonly string ChatBoxTemplate; @@ -25,35 +24,34 @@ public class RouteController Error = delegate(object? _, ErrorEventArgs args) { args.ErrorContext.Handled = true; } }; - public RouteController(Plugin plugin, HostContext core) + public RouteController(HostContext hostContext) { - Plugin = plugin; - Core = core; + HostContext = hostContext; - AuthTemplate = File.ReadAllText(Path.Combine(Core.StaticDir, "index.html")); - ChatBoxTemplate = File.ReadAllText(Path.Combine(Core.StaticDir, "chat.html")); + AuthTemplate = File.ReadAllText(Path.Combine(HostContext.StaticDir, "index.html")); + ChatBoxTemplate = File.ReadAllText(Path.Combine(HostContext.StaticDir, "chat.html")); // Pre Auth - Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/", AuthRoute, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.POST, "/auth", AuthenticateClient, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/gfdata.gfd", GetGfdData, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/fonticon_ps5.tex", GetTexData, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/FFXIV_Lodestone_SSF.ttf", GetLodestoneFont, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/favicon.ico", GetFavicon, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Parameter.Add(HttpMethod.GET, "/emote/{name}", GetEmote, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/", AuthRoute, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Static.Add(HttpMethod.POST, "/auth", AuthenticateClient, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/gfdata.gfd", GetGfdData, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/fonticon_ps5.tex", GetTexData, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/FFXIV_Lodestone_SSF.ttf", GetLodestoneFont, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/favicon.ico", GetFavicon, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Parameter.Add(HttpMethod.GET, "/emote/{name}", GetEmote, ExceptionRoute); // Post Auth - Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.GET, "/chat", ChatBoxRoute, ExceptionRoute); - Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/send", ReceiveMessage, ExceptionRoute); - Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/channel", ReceiveChannelSwitch, ExceptionRoute); - Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/tab", ReceiveTabSwitch, ExceptionRoute); + HostContext.Host.Routes.PostAuthentication.Static.Add(HttpMethod.GET, "/chat", ChatBoxRoute, ExceptionRoute); + HostContext.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/send", ReceiveMessage, ExceptionRoute); + HostContext.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/channel", ReceiveChannelSwitch, ExceptionRoute); + HostContext.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/tab", ReceiveTabSwitch, ExceptionRoute); // Ship all other static files dynamically - Core.Host.Routes.PreAuthentication.Content.Add("/_app/", true, ExceptionRoute); - Core.Host.Routes.PreAuthentication.Content.Add("/static/", true, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Content.Add("/_app/", true, ExceptionRoute); + HostContext.Host.Routes.PreAuthentication.Content.Add("/static/", true, ExceptionRoute); // Server-Sent Events Route - Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/sse", NewSSEConnection, ExceptionRoute); + HostContext.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/sse", NewSSEConnection, ExceptionRoute); } private async Task ExceptionRoute(HttpContextBase ctx, Exception _) @@ -97,7 +95,7 @@ public class RouteController private async Task GetLodestoneFont(HttpContextBase ctx) { - var data = Plugin.FontManager.GameSymFont; + var data = HostContext.Core.Plugin.FontManager.GameSymFont; await ctx.Response.Send(data); } @@ -185,8 +183,8 @@ public class RouteController await Plugin.Framework.RunOnFrameworkThread(() => { - Plugin.ChatLogWindow.Chat = content.Message; - Plugin.ChatLogWindow.SendChatBox(Plugin.CurrentTab); + HostContext.Core.Plugin.ChatLogWindow.Chat = content.Message; + HostContext.Core.Plugin.ChatLogWindow.SendChatBox(HostContext.Core.Plugin.CurrentTab); }); ctx.Response.StatusCode = 201; @@ -206,7 +204,7 @@ public class RouteController return; } - await Plugin.Framework.RunOnFrameworkThread(() => { Plugin.ChatLogWindow.SetChannel(channel.Channel); }); + await Plugin.Framework.RunOnFrameworkThread(() => { HostContext.Core.Plugin.ChatLogWindow.SetChannel(channel.Channel); }); ctx.Response.StatusCode = 201; await ctx.Response.Send(JsonConvert.SerializeObject(new OkResponse("Channel switch was initiated."))); @@ -225,7 +223,7 @@ public class RouteController return; } - await Plugin.Framework.RunOnFrameworkThread(() => { Plugin.WantedTab = tab.Index; }); + await Plugin.Framework.RunOnFrameworkThread(() => { HostContext.Core.Plugin.WantedTab = tab.Index; }); ctx.Response.StatusCode = 201; await ctx.Response.Send(JsonConvert.SerializeObject(new OkResponse("Tab switch was initiated."))); @@ -237,15 +235,15 @@ public class RouteController { Plugin.Log.Information($"Client connected: {ctx.Guid}"); - var sse = new SSEConnection(Core.TokenSource.Token); - await Core.Processing.PrepareNewClient(sse); - Core.EventConnections.Add(sse); + var sse = new SSEConnection(HostContext.TokenSource.Token); + await HostContext.Core.PrepareNewClient(sse); + HostContext.EventConnections.Add(sse); await sse.HandleEventLoop(ctx); // It should always be done after return if (sse.Done) - Core.EventConnections.Remove(sse); + HostContext.EventConnections.Remove(sse); } catch (Exception ex) { diff --git a/ChatTwo/Http/ServerCore.cs b/ChatTwo/Http/ServerCore.cs index fa4acd3..70770a5 100644 --- a/ChatTwo/Http/ServerCore.cs +++ b/ChatTwo/Http/ServerCore.cs @@ -1,20 +1,37 @@ -using ChatTwo.Code; -using ChatTwo.Http.MessageProtocol; +using ChatTwo.Http.MessageProtocol; namespace ChatTwo.Http; public class ServerCore : IAsyncDisposable { - private readonly Plugin Plugin; + public readonly Plugin Plugin; private readonly HostContext HostContext; public ServerCore(Plugin plugin) { Plugin = plugin; - HostContext = new HostContext(plugin); + HostContext = new HostContext(this); } #region SSE Helper + internal async Task PrepareNewClient(SSEConnection sse) + { + // This takes long, so keep it outside the next frame + var messages = await HostContext.Processing.GetAllMessages(); + + // Using the bulk message event to clear everything on the client side that may still exist + await Plugin.Framework.RunOnTick(() => + { + sse.OutboundQueue.Enqueue(new BulkMessagesEvent(messages)); + + sse.OutboundQueue.Enqueue(new SwitchChannelEvent(HostContext.Processing.GetCurrentChannel())); + sse.OutboundQueue.Enqueue(new ChannelListEvent(HostContext.Processing.GetValidChannels())); + + sse.OutboundQueue.Enqueue(new ChatTabSwitchedEvent(HostContext.Processing.GetCurrentTab())); + sse.OutboundQueue.Enqueue(new ChatTabListEvent(HostContext.Processing.GetAllTabs())); + }); + } + internal void SendNewMessage(Message message) { if (!HostContext.IsActive) @@ -83,8 +100,7 @@ public class ServerCore : IAsyncDisposable { Plugin.Framework.RunOnTick(() => { - var channels = Plugin.ChatLogWindow.GetValidChannels(); - var bundledResponse = new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value))); + var bundledResponse = new ChannelListEvent(HostContext.Processing.GetValidChannels()); foreach (var eventServer in HostContext.EventConnections) eventServer.OutboundQueue.Enqueue(bundledResponse); }); @@ -105,7 +121,7 @@ public class ServerCore : IAsyncDisposable Plugin.Framework.RunOnTick(async () => { foreach (var eventServer in HostContext.EventConnections) - await HostContext.Processing.PrepareNewClient(eventServer); + await HostContext.Core.PrepareNewClient(eventServer); }); } catch (Exception ex)