Merge pull request #83 from RedWizardsLab/dev_fix_refresh_token_saving

fix: save refreshed tokens for server URLs with a trailing slash
This commit is contained in:
Stone_Red
2026-11-06 17:06:47 +01:00
committed by GitHub
6 changed files with 46 additions and 9 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ Release history for EchoHub.
## Releases
- [v0.2.18](v0.2.18.md) - IRCv3 Capabilities & Native OS Notifications
- [v0.2.18](v0.2.18.md) - IRCv3 Capabilities, Native OS Notifications & Saved Session Fix
- [v0.2.17](v0.2.17.md) - Server Version Reporting & IRC Multi-Line Fix
- [v0.2.16](v0.2.16.md) - Periodic Server-Stats Report, Upload & Moderation Logging & Quieter Connection Logs
- [v0.2.15](v0.2.15.md) - Invite Codes, Data Export & Deletion, /me, /banner, Replies, Open Images In Browser & IRC Image Links
+4
View File
@@ -4,3 +4,7 @@
- **IRCv3 capabilities** — `server-time`, `message-tags`, `echo-message`, `batch` and `draft/multiline` are now advertised and fully supported in the IRC gateway. CAP LS 302 requests receive capability values. Clients can enable caps via CAP REQ and disable them with CAP REQ -cap.
- **Native OS notifications** — EchoHub now supports native notifications on Windows, macOS and Linux. Notifications are sent for mentions or replies.
## Bug Fixes
- **Saved sessions lost after a token refresh** — refreshed tokens are now saved for servers whose saved URL ends in a slash. Previously the lookup compared the URL without the slash, so the new token was never written and the next launch failed with the old, already revoked one, logging the user out.
+1 -2
View File
@@ -2232,8 +2232,7 @@ public sealed class AppOrchestrator : IDisposable
if (url is null) return;
var config = ConfigManager.Load();
var server = config.SavedServers.FirstOrDefault(s =>
string.Equals(s.Url, url, StringComparison.OrdinalIgnoreCase));
var server = config.FindServer(url);
if (server is null) return;
mutate(server);
@@ -18,6 +18,14 @@ public class ClientConfig
/// Applies to copy-paste/drag-drop attachments, which have no per-file size flag.
/// </summary>
public string DefaultAsciiSize { get; set; } = "m";
/// <summary>
/// Finds a saved server by URL. A trailing slash is ignored: entries keep the URL as the
/// user typed it, while <see cref="Services.ApiClient.BaseUrl"/> has the slash removed.
/// </summary>
public SavedServer? FindServer(string url) =>
SavedServers.FirstOrDefault(s =>
string.Equals(s.Url.TrimEnd('/'), url.TrimEnd('/'), StringComparison.OrdinalIgnoreCase));
}
public class NotificationConfig
@@ -152,7 +152,7 @@ internal sealed class ConnectionManager : IAsyncDisposable
// all of them, not just channels opened this session. Channels the user left
// with /leave stay out until rejoined; protected channels we can't enter
// silently (no cached membership) are skipped, never prompted for.
var leftChannels = FindServer(ConfigManager.Load(), info.ServerUrl)?.LeftChannels ?? [];
var leftChannels = ConfigManager.Load().FindServer(info.ServerUrl)?.LeftChannels ?? [];
foreach (var channel in channels)
{
if (channel.Name.Equals(HubConstants.DefaultChannel, StringComparison.OrdinalIgnoreCase))
@@ -332,7 +332,7 @@ internal sealed class ConnectionManager : IAsyncDisposable
{
if (_apiClient?.RefreshToken is null) return;
var config = ConfigManager.Load();
var server = FindServer(config, _apiClient.BaseUrl);
var server = config.FindServer(_apiClient.BaseUrl);
if (server is not null && server.RememberMe)
{
server.RefreshToken = _apiClient.RefreshToken;
@@ -340,10 +340,6 @@ internal sealed class ConnectionManager : IAsyncDisposable
}
}
private static SavedServer? FindServer(ClientConfig config, string url) =>
config.SavedServers.FirstOrDefault(s =>
string.Equals(s.Url, url, StringComparison.OrdinalIgnoreCase));
// ── Dispose ───────────────────────────────────────────────────────────
public async ValueTask DisposeAsync()
+30
View File
@@ -0,0 +1,30 @@
using EchoHub.Client.Config;
using Xunit;
namespace EchoHub.Tests;
public class ClientConfigTests
{
[Theory]
[InlineData("https://echo.example.com/", "https://echo.example.com")]
[InlineData("https://echo.example.com", "https://echo.example.com/")]
[InlineData("https://echo.example.com", "https://echo.example.com")]
[InlineData("https://ECHO.example.com/", "https://echo.example.com")]
public void FindServer_IgnoresTrailingSlashAndCase(string savedUrl, string lookupUrl)
{
var config = new ClientConfig();
var server = new SavedServer { Name = "echo", Url = savedUrl };
config.SavedServers.Add(server);
Assert.Same(server, config.FindServer(lookupUrl));
}
[Fact]
public void FindServer_OtherServer_ReturnsNull()
{
var config = new ClientConfig();
config.SavedServers.Add(new SavedServer { Name = "echo", Url = "https://echo.example.com/" });
Assert.Null(config.FindServer("https://other.example.com"));
}
}