- Implement better start/stop for the webinterface
- Save session tokens between startups
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
using ChatTwo.GameFunctions.Types;
|
using ChatTwo.GameFunctions.Types;
|
||||||
using ChatTwo.Resources;
|
using ChatTwo.Resources;
|
||||||
@@ -122,7 +123,10 @@ internal class Configuration : IPluginConfiguration
|
|||||||
|
|
||||||
// Webinterface
|
// Webinterface
|
||||||
public bool WebinterfaceEnabled;
|
public bool WebinterfaceEnabled;
|
||||||
|
public bool WebinterfaceAutoStart;
|
||||||
public string WebinterfacePassword = WebinterfaceUtil.GenerateSimpleAuthCode();
|
public string WebinterfacePassword = WebinterfaceUtil.GenerateSimpleAuthCode();
|
||||||
|
public int WebinterfacePort = 9000;
|
||||||
|
public ConcurrentDictionary<string, bool> SessionTokens = [];
|
||||||
|
|
||||||
internal void UpdateFrom(Configuration other, bool backToOriginal)
|
internal void UpdateFrom(Configuration other, bool backToOriginal)
|
||||||
{
|
{
|
||||||
@@ -139,7 +143,7 @@ internal class Configuration : IPluginConfiguration
|
|||||||
HideWhenInactive = other.HideWhenInactive;
|
HideWhenInactive = other.HideWhenInactive;
|
||||||
InactivityHideTimeout = other.InactivityHideTimeout;
|
InactivityHideTimeout = other.InactivityHideTimeout;
|
||||||
InactivityHideActiveDuringBattle = other.InactivityHideActiveDuringBattle;
|
InactivityHideActiveDuringBattle = other.InactivityHideActiveDuringBattle;
|
||||||
InactivityHideChannels = other.InactivityHideChannels.ToDictionary(entry => entry.Key, entry => entry.Value);
|
InactivityHideChannels = other.InactivityHideChannels?.ToDictionary(entry => entry.Key, entry => entry.Value);
|
||||||
InactivityHideExtraChatAll = other.InactivityHideExtraChatAll;
|
InactivityHideExtraChatAll = other.InactivityHideExtraChatAll;
|
||||||
InactivityHideExtraChatChannels = other.InactivityHideExtraChatChannels.ToHashSet();
|
InactivityHideExtraChatChannels = other.InactivityHideExtraChatChannels.ToHashSet();
|
||||||
ShowHideButton = other.ShowHideButton;
|
ShowHideButton = other.ShowHideButton;
|
||||||
@@ -188,7 +192,9 @@ internal class Configuration : IPluginConfiguration
|
|||||||
ChatTabForward = other.ChatTabForward;
|
ChatTabForward = other.ChatTabForward;
|
||||||
ChatTabBackward = other.ChatTabBackward;
|
ChatTabBackward = other.ChatTabBackward;
|
||||||
WebinterfaceEnabled = other.WebinterfaceEnabled;
|
WebinterfaceEnabled = other.WebinterfaceEnabled;
|
||||||
|
WebinterfaceAutoStart = other.WebinterfaceAutoStart;
|
||||||
WebinterfacePassword = other.WebinterfacePassword;
|
WebinterfacePassword = other.WebinterfacePassword;
|
||||||
|
WebinterfacePort = other.WebinterfacePort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
using WatsonWebserver.Core;
|
||||||
|
using WatsonWebserver.Lite;
|
||||||
|
|
||||||
|
namespace ChatTwo.Http;
|
||||||
|
|
||||||
|
public class HostContext
|
||||||
|
{
|
||||||
|
private readonly Plugin Plugin;
|
||||||
|
|
||||||
|
public bool IsActive;
|
||||||
|
public bool IsStopping;
|
||||||
|
|
||||||
|
internal WebserverLite Host;
|
||||||
|
internal Processing Processing;
|
||||||
|
internal RouteController RouteController;
|
||||||
|
|
||||||
|
internal readonly List<SSEConnection> EventConnections = [];
|
||||||
|
|
||||||
|
internal readonly CancellationTokenSource TokenSource = new();
|
||||||
|
internal readonly string StaticDir = Path.Combine(Plugin.Interface.AssemblyLocation.DirectoryName!, "Http");
|
||||||
|
|
||||||
|
public HostContext(Plugin plugin)
|
||||||
|
{
|
||||||
|
Plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Host = new WebserverLite(new WebserverSettings("*", Plugin.Config.WebinterfacePort), DefaultRoute);
|
||||||
|
|
||||||
|
Processing = new Processing(Plugin);
|
||||||
|
RouteController = new RouteController(Plugin, this);
|
||||||
|
|
||||||
|
Host.Routes.PreAuthentication.Content.BaseDirectory = StaticDir;
|
||||||
|
Host.Routes.AuthenticateRequest = CheckAuthenticationCookie;
|
||||||
|
Host.Events.ExceptionEncountered += ExceptionEncountered;
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
Host.Settings.Debug.Requests = true;
|
||||||
|
Host.Settings.Debug.Routing = true;
|
||||||
|
Host.Settings.Debug.Responses = true;
|
||||||
|
Host.Settings.Debug.AccessControl = true;
|
||||||
|
Host.Events.Logger = logMessage => Plugin.Log.Information(logMessage);
|
||||||
|
|
||||||
|
IsActive = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
Plugin.Log.Error(ex, "Initialization of the webserver failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Host.Start(TokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Log.Error(ex, "Webserver failed to boot up.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> Stop()
|
||||||
|
{
|
||||||
|
// Is already stopped
|
||||||
|
if (!IsActive)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
IsStopping = true;
|
||||||
|
await Task.Delay(10000);
|
||||||
|
Host.Stop();
|
||||||
|
|
||||||
|
// Save our session tokens
|
||||||
|
Plugin.SaveConfig();
|
||||||
|
|
||||||
|
// We get a copy, so that the original can be cleaned up successfully
|
||||||
|
foreach (var eventServer in EventConnections.ToArray())
|
||||||
|
await eventServer.DisposeAsync();
|
||||||
|
|
||||||
|
EventConnections.Clear();
|
||||||
|
Host.Dispose();
|
||||||
|
RouteController.Dispose();
|
||||||
|
IsStopping = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Log.Error(ex, "Webserver failed to stop and dispose all resources.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region GeneralHandlers
|
||||||
|
private static void ExceptionEncountered(object? _, ExceptionEventArgs args)
|
||||||
|
{
|
||||||
|
Plugin.Log.Error(args.Exception, "Webserver threw an exception.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DefaultRoute(HttpContextBase ctx)
|
||||||
|
{
|
||||||
|
await ctx.Response.Send("Nothing to see here.");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private async Task CheckAuthenticationCookie(HttpContextBase ctx)
|
||||||
|
{
|
||||||
|
if (Plugin.Config.SessionTokens.IsEmpty)
|
||||||
|
{
|
||||||
|
await RouteController.Redirect(ctx, "/", ("message", "Invalid session token."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookies = WebserverUtil.GetCookieData(ctx.Request.Headers.Get("Cookie") ?? "");
|
||||||
|
if (!cookies.TryGetValue("ChatTwo-token", out var token) || !Plugin.Config.SessionTokens.ContainsKey(token))
|
||||||
|
await RouteController.Redirect(ctx, "/", ("message", "Invalid session token."));
|
||||||
|
|
||||||
|
// Do nothing to let auth pass
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,20 +14,19 @@ namespace ChatTwo.Http;
|
|||||||
public class RouteController
|
public class RouteController
|
||||||
{
|
{
|
||||||
private readonly Plugin Plugin;
|
private readonly Plugin Plugin;
|
||||||
private readonly ServerCore Core;
|
private readonly HostContext Core;
|
||||||
|
|
||||||
private readonly string AuthTemplate;
|
private readonly string AuthTemplate;
|
||||||
private readonly string ChatBoxTemplate;
|
private readonly string ChatBoxTemplate;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, long> RateLimit = [];
|
private readonly ConcurrentDictionary<string, long> RateLimit = [];
|
||||||
internal readonly ConcurrentDictionary<string, bool> SessionTokens = [];
|
|
||||||
|
|
||||||
private readonly JsonSerializerSettings JsonSettings = new()
|
private readonly JsonSerializerSettings JsonSettings = new()
|
||||||
{
|
{
|
||||||
Error = delegate(object? sender, ErrorEventArgs args) { args.ErrorContext.Handled = true; }
|
Error = delegate(object? _, ErrorEventArgs args) { args.ErrorContext.Handled = true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public RouteController(Plugin plugin, ServerCore core)
|
public RouteController(Plugin plugin, HostContext core)
|
||||||
{
|
{
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
Core = core;
|
Core = core;
|
||||||
@@ -36,21 +35,21 @@ public class RouteController
|
|||||||
ChatBoxTemplate = File.ReadAllText(Path.Combine(Core.StaticDir, "templates", "chat.html"));
|
ChatBoxTemplate = File.ReadAllText(Path.Combine(Core.StaticDir, "templates", "chat.html"));
|
||||||
|
|
||||||
// Pre Auth
|
// Pre Auth
|
||||||
Core.HostContext.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/", AuthRoute, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/", AuthRoute, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PreAuthentication.Static.Add(HttpMethod.POST, "/auth", AuthenticateClient, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.POST, "/auth", AuthenticateClient, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/gfdata.gfd", GetGfdData, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/gfdata.gfd", GetGfdData, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/fonticon_ps5.tex", GetTexData, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/fonticon_ps5.tex", GetTexData, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/FFXIV_Lodestone_SSF.ttf", GetLodestoneFont, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Static.Add(HttpMethod.GET, "/files/FFXIV_Lodestone_SSF.ttf", GetLodestoneFont, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PreAuthentication.Parameter.Add(HttpMethod.GET, "/emote/{name}", GetEmote, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Parameter.Add(HttpMethod.GET, "/emote/{name}", GetEmote, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PreAuthentication.Content.Add("/static", true, ExceptionRoute);
|
Core.Host.Routes.PreAuthentication.Content.Add("/static", true, ExceptionRoute);
|
||||||
|
|
||||||
// Post Auth
|
// Post Auth
|
||||||
Core.HostContext.Routes.PostAuthentication.Static.Add(HttpMethod.GET, "/chat", ChatBoxRoute, ExceptionRoute);
|
Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.GET, "/chat", ChatBoxRoute, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/send", ReceiveMessage, ExceptionRoute);
|
Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/send", ReceiveMessage, ExceptionRoute);
|
||||||
Core.HostContext.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/channel", ReceiveChannelSwitch, ExceptionRoute);
|
Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.POST, "/channel", ReceiveChannelSwitch, ExceptionRoute);
|
||||||
|
|
||||||
// Server-Sent Events Route
|
// Server-Sent Events Route
|
||||||
Core.HostContext.Routes.PostAuthentication.Static.Add(HttpMethod.GET, "/sse", NewSSEConnection, ExceptionRoute);
|
Core.Host.Routes.PostAuthentication.Static.Add(HttpMethod.GET, "/sse", NewSSEConnection, ExceptionRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExceptionRoute(HttpContextBase ctx, Exception _)
|
private async Task ExceptionRoute(HttpContextBase ctx, Exception _)
|
||||||
@@ -135,7 +134,7 @@ public class RouteController
|
|||||||
return await Redirect(ctx, "/", ("message", "Authentication failed."));
|
return await Redirect(ctx, "/", ("message", "Authentication failed."));
|
||||||
|
|
||||||
var token = WebinterfaceUtil.GenerateSimpleToken();
|
var token = WebinterfaceUtil.GenerateSimpleToken();
|
||||||
SessionTokens.TryAdd(token, true);
|
Plugin.Config.SessionTokens.TryAdd(token, true);
|
||||||
|
|
||||||
ctx.Response.Headers.Add("Set-Cookie", $"ChatTwo-token={token}");
|
ctx.Response.Headers.Add("Set-Cookie", $"ChatTwo-token={token}");
|
||||||
return await Redirect(ctx, "/chat");
|
return await Redirect(ctx, "/chat");
|
||||||
|
|||||||
+45
-81
@@ -1,52 +1,29 @@
|
|||||||
using ChatTwo.Http.MessageProtocol;
|
using ChatTwo.Http.MessageProtocol;
|
||||||
using WatsonWebserver.Core;
|
|
||||||
using WatsonWebserver.Lite;
|
|
||||||
using ExceptionEventArgs = WatsonWebserver.Core.ExceptionEventArgs;
|
|
||||||
|
|
||||||
namespace ChatTwo.Http;
|
namespace ChatTwo.Http;
|
||||||
|
|
||||||
public class ServerCore : IAsyncDisposable
|
public class ServerCore : IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly Plugin Plugin;
|
private readonly Plugin Plugin;
|
||||||
internal readonly Processing Processing;
|
private readonly HostContext HostContext;
|
||||||
internal readonly RouteController RouteController;
|
|
||||||
|
|
||||||
internal readonly WebserverLite HostContext;
|
|
||||||
|
|
||||||
internal readonly CancellationTokenSource TokenSource = new();
|
|
||||||
internal readonly string StaticDir = Path.Combine(Plugin.Interface.AssemblyLocation.DirectoryName!, "Http");
|
|
||||||
|
|
||||||
internal readonly List<SSEConnection> EventConnections = [];
|
|
||||||
|
|
||||||
public ServerCore(Plugin plugin)
|
public ServerCore(Plugin plugin)
|
||||||
{
|
{
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
HostContext = new WebserverLite(new WebserverSettings("*", 9000), DefaultRoute);
|
HostContext = new HostContext(plugin);
|
||||||
|
|
||||||
Processing = new Processing(plugin);
|
|
||||||
RouteController = new RouteController(plugin, this);
|
|
||||||
|
|
||||||
HostContext.Routes.PreAuthentication.Content.BaseDirectory = StaticDir;
|
|
||||||
HostContext.Routes.AuthenticateRequest = CheckAuthenticationCookie;
|
|
||||||
HostContext.Events.ExceptionEncountered += ExceptionEncountered;
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
HostContext.Settings.Debug.Requests = true;
|
|
||||||
HostContext.Settings.Debug.Routing = true;
|
|
||||||
HostContext.Settings.Debug.Responses = true;
|
|
||||||
HostContext.Settings.Debug.AccessControl = true;
|
|
||||||
HostContext.Events.Logger = logMessage => Plugin.Log.Information(logMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region SSEFunctions
|
#region SSE Helper
|
||||||
internal void SendNewMessage(Message message)
|
internal void SendNewMessage(Message message)
|
||||||
{
|
{
|
||||||
|
if (!HostContext.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin.Framework.RunOnTick(() =>
|
Plugin.Framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
var bundledResponse = new NewMessageEvent(new Messages([Processing.ReadMessageContent(message)]));
|
var bundledResponse = new NewMessageEvent(new Messages([HostContext.Processing.ReadMessageContent(message)]));
|
||||||
foreach (var eventServer in EventConnections)
|
foreach (var eventServer in HostContext.EventConnections)
|
||||||
eventServer.OutboundQueue.Enqueue(bundledResponse);
|
eventServer.OutboundQueue.Enqueue(bundledResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -58,12 +35,15 @@ public class ServerCore : IAsyncDisposable
|
|||||||
|
|
||||||
internal void SendBulkMessageList()
|
internal void SendBulkMessageList()
|
||||||
{
|
{
|
||||||
|
if (!HostContext.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin.Framework.RunOnTick(() =>
|
Plugin.Framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
foreach (var eventServer in EventConnections)
|
foreach (var eventServer in HostContext.EventConnections)
|
||||||
eventServer.OutboundQueue.Enqueue(new BulkMessagesEvent(new Messages(Processing.ReadMessageList().Result)));
|
eventServer.OutboundQueue.Enqueue(new BulkMessagesEvent(new Messages(HostContext.Processing.ReadMessageList().Result)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -74,12 +54,15 @@ public class ServerCore : IAsyncDisposable
|
|||||||
|
|
||||||
internal void SendChannelSwitch(Chunk[] channelName)
|
internal void SendChannelSwitch(Chunk[] channelName)
|
||||||
{
|
{
|
||||||
|
if (!HostContext.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin.Framework.RunOnTick(() =>
|
Plugin.Framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
var bundledResponse = new SwitchChannelEvent(new SwitchChannel(Processing.ReadChannelName(channelName)));
|
var bundledResponse = new SwitchChannelEvent(new SwitchChannel(HostContext.Processing.ReadChannelName(channelName)));
|
||||||
foreach (var eventServer in EventConnections)
|
foreach (var eventServer in HostContext.EventConnections)
|
||||||
eventServer.OutboundQueue.Enqueue(bundledResponse);
|
eventServer.OutboundQueue.Enqueue(bundledResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,13 +74,16 @@ public class ServerCore : IAsyncDisposable
|
|||||||
|
|
||||||
internal void SendChannelList()
|
internal void SendChannelList()
|
||||||
{
|
{
|
||||||
|
if (!HostContext.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin.Framework.RunOnTick(() =>
|
Plugin.Framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
var channels = Plugin.ChatLogWindow.GetAvailableChannels();
|
var channels = Plugin.ChatLogWindow.GetAvailableChannels();
|
||||||
var bundledResponse = new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value)));
|
var bundledResponse = new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value)));
|
||||||
foreach (var eventServer in EventConnections)
|
foreach (var eventServer in HostContext.EventConnections)
|
||||||
eventServer.OutboundQueue.Enqueue(bundledResponse);
|
eventServer.OutboundQueue.Enqueue(bundledResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -108,65 +94,43 @@ public class ServerCore : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GeneralHandlers
|
|
||||||
private static void ExceptionEncountered(object? _, ExceptionEventArgs args)
|
|
||||||
{
|
|
||||||
Plugin.Log.Error(args.Exception, "Webserver threw an exception.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DefaultRoute(HttpContextBase ctx)
|
|
||||||
{
|
|
||||||
await ctx.Response.Send("Nothing to see here.");
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private async Task CheckAuthenticationCookie(HttpContextBase ctx)
|
|
||||||
{
|
|
||||||
if (RouteController.SessionTokens.IsEmpty)
|
|
||||||
{
|
|
||||||
await RouteController.Redirect(ctx, "/", ("message", "Invalid session token."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookies = WebserverUtil.GetCookieData(ctx.Request.Headers.Get("Cookie") ?? "");
|
|
||||||
if (!cookies.TryGetValue("ChatTwo-token", out var token) || !RouteController.SessionTokens.ContainsKey(token))
|
|
||||||
await RouteController.Redirect(ctx, "/", ("message", "Invalid session token."));
|
|
||||||
|
|
||||||
// Do nothing to let auth pass
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InvalidateSessions()
|
public void InvalidateSessions()
|
||||||
{
|
{
|
||||||
RouteController.SessionTokens.Clear();
|
if (!HostContext.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Plugin.Config.SessionTokens.Clear();
|
||||||
|
Plugin.SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetStats()
|
public bool IsActive()
|
||||||
{
|
{
|
||||||
return HostContext.IsListening;
|
return HostContext is { IsActive: true, Host.IsListening: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public bool IsStopping()
|
||||||
{
|
{
|
||||||
try
|
return HostContext is { IsActive: false, IsStopping: true };
|
||||||
{
|
|
||||||
HostContext.Start(TokenSource.Token);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
|
||||||
|
public bool Start()
|
||||||
{
|
{
|
||||||
Plugin.Log.Error(ex, "Startup failed with an error.");
|
return HostContext.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
HostContext.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> Stop()
|
||||||
|
{
|
||||||
|
return await HostContext.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
await TokenSource.CancelAsync();
|
await HostContext.DisposeAsync();
|
||||||
HostContext.Stop();
|
|
||||||
|
|
||||||
// We get a copy, so that the original can be cleaned up succesfully
|
|
||||||
foreach (var eventServer in EventConnections.ToArray())
|
|
||||||
await eventServer.DisposeAsync();
|
|
||||||
|
|
||||||
HostContext.Dispose();
|
|
||||||
RouteController.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-1
@@ -131,7 +131,16 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
ServerCore = new ServerCore(this);
|
ServerCore = new ServerCore(this);
|
||||||
Task.Run(() => ServerCore.Start());
|
|
||||||
|
// Automatically start the webserver if requested
|
||||||
|
if (Config.WebinterfaceAutoStart)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
ServerCore.Start();
|
||||||
|
ServerCore.Run();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+98
-8
@@ -3416,6 +3416,24 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The Webinterface is going to be started when the plugins loads..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Options_WebinterfaceAutoStart_Description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Options_WebinterfaceAutoStart_Description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Start automatically.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Options_WebinterfaceAutoStart_Name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Options_WebinterfaceAutoStart_Name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Enables the webinterface that can be accessed with a browser..
|
/// Looks up a localized string similar to Enables the webinterface that can be accessed with a browser..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3426,7 +3444,7 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Enable.
|
/// Looks up a localized string similar to Enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Options_WebinterfaceEnable_Name {
|
internal static string Options_WebinterfaceEnable_Name {
|
||||||
get {
|
get {
|
||||||
@@ -3570,7 +3588,43 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Your current password to access the webinterface:.
|
/// Looks up a localized string similar to Start.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Button_Start {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Button_Start", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Stop.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Button_Stop {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Button_Stop", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Controls:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Controls {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Controls", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Active:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Controls_Active {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Controls_Active", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Authcode:.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Webinterface_CurrentPassword {
|
internal static string Webinterface_CurrentPassword {
|
||||||
get {
|
get {
|
||||||
@@ -3578,6 +3632,24 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The port this webinterface will be running on..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Option_Port_Description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Option_Port_Description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Port.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Option_Port_Name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Option_Port_Name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Reset your password and invalidate all session tokens..
|
/// Looks up a localized string similar to Reset your password and invalidate all session tokens..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3588,20 +3660,38 @@ namespace ChatTwo.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Webinterface Status:.
|
/// Looks up a localized string similar to Webinterface failed to start. Check /xllog for more information..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Webinterface_Status {
|
internal static string Webinterface_Start_Failed {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Webinterface_Status", resourceCulture);
|
return ResourceManager.GetString("Webinterface_Start_Failed", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Active:.
|
/// Looks up a localized string similar to Webinterface was started..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Webinterface_Status_Active {
|
internal static string Webinterface_Start_Success {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Webinterface_Status_Active", resourceCulture);
|
return ResourceManager.GetString("Webinterface_Start_Success", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Webinterface failed to stop. Check /xllog for more information..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Stop_Failed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Stop_Failed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Webinterface was stopped..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Webinterface_Stop_Success {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Webinterface_Stop_Success", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -551,11 +551,17 @@
|
|||||||
<value>If this is enabled, the Auto Translate list will be sorted alphabetically.</value>
|
<value>If this is enabled, the Auto Translate list will be sorted alphabetically.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_WebinterfaceEnable_Name">
|
<data name="Options_WebinterfaceEnable_Name">
|
||||||
<value>Enable</value>
|
<value>Enabled</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_WebinterfaceEnable_Description">
|
<data name="Options_WebinterfaceEnable_Description">
|
||||||
<value>Enables the webinterface that can be accessed with a browser.</value>
|
<value>Enables the webinterface that can be accessed with a browser.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Options_WebinterfaceAutoStart_Name">
|
||||||
|
<value>Start automatically</value>
|
||||||
|
</data>
|
||||||
|
<data name="Options_WebinterfaceAutoStart_Description">
|
||||||
|
<value>The Webinterface is going to be started when the plugins loads.</value>
|
||||||
|
</data>
|
||||||
<data name="Options_OverrideStyle_Name">
|
<data name="Options_OverrideStyle_Name">
|
||||||
<value>Override Style</value>
|
<value>Override Style</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -569,15 +575,15 @@
|
|||||||
<value>Cycle chat tab backwards keybind</value>
|
<value>Cycle chat tab backwards keybind</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Webinterface_CurrentPassword">
|
<data name="Webinterface_CurrentPassword">
|
||||||
<value>Your current password to access the webinterface:</value>
|
<value>Authcode:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Webinterface_PasswordReset_Tooltip">
|
<data name="Webinterface_PasswordReset_Tooltip">
|
||||||
<value>Reset your password and invalidate all session tokens.</value>
|
<value>Reset your password and invalidate all session tokens.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Webinterface_Status">
|
<data name="Webinterface_Controls">
|
||||||
<value>Webinterface Status:</value>
|
<value>Controls:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Webinterface_Status_Active">
|
<data name="Webinterface_Controls_Active">
|
||||||
<value>Active:</value>
|
<value>Active:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Keybind_None">
|
<data name="Keybind_None">
|
||||||
@@ -1303,4 +1309,28 @@
|
|||||||
<data name="Chat_SendTell_Error" xml:space="preserve">
|
<data name="Chat_SendTell_Error" xml:space="preserve">
|
||||||
<value>An error occured while sending this tell message</value>
|
<value>An error occured while sending this tell message</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Webinterface_Stop_Success" xml:space="preserve">
|
||||||
|
<value>Webinterface was stopped.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Stop_Failed" xml:space="preserve">
|
||||||
|
<value>Webinterface failed to stop. Check /xllog for more information.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Button_Stop" xml:space="preserve">
|
||||||
|
<value>Stop</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Button_Start" xml:space="preserve">
|
||||||
|
<value>Start</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Start_Success" xml:space="preserve">
|
||||||
|
<value>Webinterface was started.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Start_Failed" xml:space="preserve">
|
||||||
|
<value>Webinterface failed to start. Check /xllog for more information.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Option_Port_Name" xml:space="preserve">
|
||||||
|
<value>Port</value>
|
||||||
|
</data>
|
||||||
|
<data name="Webinterface_Option_Port_Description" xml:space="preserve">
|
||||||
|
<value>The port this webinterface will be running on.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using ChatTwo.Resources;
|
|||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
@@ -37,8 +38,7 @@ internal sealed class Webinterface(Plugin plugin, Configuration mutable) : ISett
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGui.Checkbox(Language.Options_WebinterfaceEnable_Name, ref Mutable.WebinterfaceEnabled);
|
ImGuiUtil.OptionCheckbox(ref Mutable.WebinterfaceEnabled, Language.Options_WebinterfaceEnable_Name, Language.Options_WebinterfaceEnable_Description);
|
||||||
ImGuiUtil.HelpText(Language.Options_WebinterfaceEnable_Description);
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
if (!Mutable.WebinterfaceEnabled)
|
if (!Mutable.WebinterfaceEnabled)
|
||||||
@@ -47,6 +47,13 @@ internal sealed class Webinterface(Plugin plugin, Configuration mutable) : ISett
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
ImGuiUtil.OptionCheckbox(ref Mutable.WebinterfaceAutoStart, Language.Options_WebinterfaceAutoStart_Name, Language.Options_WebinterfaceAutoStart_Description);
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
if (ImGuiUtil.InputIntVertical(Language.Webinterface_Option_Port_Name, Language.Webinterface_Option_Port_Description, ref Mutable.WebinterfacePort))
|
||||||
|
Mutable.WebinterfacePort = Math.Clamp(Mutable.WebinterfacePort, 1024, 49151);
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGuiUtil.WrappedTextWithColor(ImGuiColors.DalamudOrange, Language.Webinterface_CurrentPassword);
|
ImGuiUtil.WrappedTextWithColor(ImGuiColors.DalamudOrange, Language.Webinterface_CurrentPassword);
|
||||||
ImGui.TextUnformatted(Mutable.WebinterfacePassword);
|
ImGui.TextUnformatted(Mutable.WebinterfacePassword);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -56,18 +63,56 @@ internal sealed class Webinterface(Plugin plugin, Configuration mutable) : ISett
|
|||||||
Plugin.ServerCore.InvalidateSessions();
|
Plugin.ServerCore.InvalidateSessions();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.WrappedTextWithColor(ImGuiColors.HealerGreen, Language.Webinterface_Status);
|
ImGuiUtil.WrappedTextWithColor(ImGuiColors.HealerGreen, Language.Webinterface_Controls);
|
||||||
using (ImRaii.PushIndent(10.0f))
|
using (ImRaii.PushIndent(10.0f))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted(Language.Webinterface_Status_Active);
|
ImGui.TextUnformatted(Language.Webinterface_Controls_Active);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
var isActive = Plugin.ServerCore.GetStats();
|
var isActive = Plugin.ServerCore.IsActive();
|
||||||
using (Plugin.FontManager.FontAwesome.Push())
|
using (Plugin.FontManager.FontAwesome.Push())
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, isActive ? ImGuiColors.HealerGreen : ImGuiColors.DalamudOrange))
|
using (ImRaii.PushColor(ImGuiCol.Text, isActive ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted($"{(isActive ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Cross.ToIconString())}");
|
ImGui.TextUnformatted(isActive ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString());
|
||||||
|
}
|
||||||
|
|
||||||
|
using (ImRaii.Disabled(isActive || Plugin.ServerCore.IsStopping()))
|
||||||
|
{
|
||||||
|
if (ImGui.Button(Language.Webinterface_Button_Start))
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var ok = Plugin.ServerCore.Start();
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
Plugin.ServerCore.Run();
|
||||||
|
WrapperUtil.AddNotification(Language.Webinterface_Start_Success, NotificationType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WrapperUtil.AddNotification(Language.Webinterface_Start_Failed, NotificationType.Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
using (ImRaii.Disabled(!isActive || Plugin.ServerCore.IsStopping()))
|
||||||
|
{
|
||||||
|
if (ImGui.Button(Language.Webinterface_Button_Stop))
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var ok = await Plugin.ServerCore.Stop();
|
||||||
|
if (ok)
|
||||||
|
WrapperUtil.AddNotification(Language.Webinterface_Stop_Success, NotificationType.Success);
|
||||||
|
else
|
||||||
|
WrapperUtil.AddNotification(Language.Webinterface_Stop_Failed, NotificationType.Error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ImGui.Spacing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user