about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--MatrixRoomUtils.Core/AuthenticatedHomeServer.cs53
-rw-r--r--MatrixRoomUtils.Core/Authentication/MatrixAuth.cs (renamed from MatrixRoomUtils.Core/Authentication/MatrixAccount.cs)5
-rw-r--r--MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs19
-rw-r--r--MatrixRoomUtils.Core/Extensions/StringExtensions.cs2
-rw-r--r--MatrixRoomUtils.Core/Interfaces/IHomeServer.cs37
-rw-r--r--MatrixRoomUtils.Core/RatelimitedHttpClient.cs7
-rw-r--r--MatrixRoomUtils.Core/Responses/LoginResponse.cs2
-rw-r--r--MatrixRoomUtils.Core/Room.cs44
-rw-r--r--MatrixRoomUtils.Core/RuntimeCache.cs29
-rw-r--r--MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs35
-rw-r--r--MatrixRoomUtils.Web/Classes/RuntimeStorage.cs55
-rw-r--r--MatrixRoomUtils.Web/Pages/DataExportPage.razor28
-rw-r--r--MatrixRoomUtils.Web/Pages/Index.razor10
-rw-r--r--MatrixRoomUtils.Web/Pages/LoginPage.razor12
-rw-r--r--MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor8
-rw-r--r--MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor75
-rw-r--r--MatrixRoomUtils.Web/Pages/RoomStateEditorPage.razor177
-rw-r--r--MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor73
-rw-r--r--MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor8
-rw-r--r--MatrixRoomUtils.Web/Pages/UserImportPage.razor14
-rw-r--r--MatrixRoomUtils.Web/Shared/IndexComponents/IndexUserItem.razor13
-rw-r--r--MatrixRoomUtils.Web/Shared/MainLayout.razor4
22 files changed, 564 insertions, 146 deletions
diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
new file mode 100644
index 0000000..dd9aa25
--- /dev/null
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -0,0 +1,53 @@
+using System.Net.Http.Headers;
+using System.Net.Http.Json;
+using System.Text.Json;
+
+namespace MatrixRoomUtils;
+
+public class AuthenticatedHomeServer : IHomeServer
+{
+    public string UserId { get; set; }
+    public string AccessToken { get; set; }
+
+    public AuthenticatedHomeServer(string userId, string accessToken, string canonicalHomeServerDomain)
+    {
+        UserId = userId;
+        AccessToken = accessToken;
+        HomeServerDomain = canonicalHomeServerDomain;
+        _httpClient = new HttpClient();
+        
+        var rhsfwt = ResolveHomeserverFromWellKnown(canonicalHomeServerDomain);
+        rhsfwt.ContinueWith(_ =>
+        {
+            FullHomeServerDomain = rhsfwt.Result;
+            _httpClient.Dispose();
+            _httpClient = new HttpClient {BaseAddress = new Uri(FullHomeServerDomain)};
+            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
+            Console.WriteLine("[AHS] Finished setting up http client :)");
+        });
+    }
+
+    public async Task<Room> GetRoom(string roomId)
+    {
+        return new Room(_httpClient, roomId);
+    }
+
+    public async Task<List<Room>> GetJoinedRooms()
+    {
+        var rooms = new List<Room>();
+        var _rooms = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
+        if (!_rooms.IsSuccessStatusCode)
+        {
+            Console.WriteLine($"Failed to get rooms: {await _rooms.Content.ReadAsStringAsync()}");
+            throw new InvalidDataException($"Failed to get rooms: {await _rooms.Content.ReadAsStringAsync()}");
+        }
+
+        var roomsJson = await _rooms.Content.ReadFromJsonAsync<JsonElement>();
+        foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray())
+        {
+            rooms.Add(new Room(_httpClient, room.GetString()));
+        }
+
+        return rooms;
+    }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAccount.cs b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
index 4180df5..687ea07 100644
--- a/MatrixRoomUtils.Core/Authentication/MatrixAccount.cs
+++ b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
@@ -4,7 +4,7 @@ using MatrixRoomUtils.Responses;
 
 namespace MatrixRoomUtils.Authentication;
 
-public class MatrixAccount
+public class MatrixAuth
 {
     public static async Task<LoginResponse> Login(string homeserver, string username, string password)
     {
@@ -43,13 +43,14 @@ public class MatrixAccount
     {
         Console.WriteLine($"Fetching profile for {mxid} on {homeserver}...");
         homeserver = await ResolveHomeserverFromWellKnown(homeserver);
-        var hc = new HttpClient();
+        using var hc = new HttpClient();
         var resp = await hc.GetAsync($"{homeserver}/_matrix/client/r0/profile/{mxid}");
         var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
         if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data.ToString());
         return data.Deserialize<ProfileResponse>();
     }
 
+    [Obsolete("Use IHomeServer")]
     public static async Task<string> ResolveHomeserverFromWellKnown(string homeserver)
     {
         using var hc = new HttpClient();
diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
new file mode 100644
index 0000000..66a5133
--- /dev/null
+++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
@@ -0,0 +1,19 @@
+namespace MatrixRoomUtils.Extensions;
+
+public static class HttpClientExtensions
+{
+    public static async Task<bool> CheckSuccessStatus(this HttpClient hc, string url)
+    {
+        //cors causes failure, try to catch
+        try
+        {
+            var resp = await hc.GetAsync(url);
+            return resp.IsSuccessStatusCode;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Failed to check success status: {e.Message}");
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/StringExtensions.cs b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
index e02f0b9..27d8265 100644
--- a/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
@@ -11,7 +11,7 @@ public static class StringExtensions
         
         var server = MxcUrl.Split('/')[2];
         var mediaId = MxcUrl.Split('/')[3];
-        return $"{await MatrixAccount.ResolveHomeserverFromWellKnown(server)}/_matrix/media/v3/download/{server}/{mediaId}";
+        return $"{await MatrixAuth.ResolveHomeserverFromWellKnown(server)}/_matrix/media/v3/download/{server}/{mediaId}";
     }
     
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
new file mode 100644
index 0000000..84714f7
--- /dev/null
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -0,0 +1,37 @@
+using System.Net.Http.Json;
+using System.Text.Json;
+using MatrixRoomUtils.Extensions;
+
+namespace MatrixRoomUtils;
+
+public class IHomeServer
+{
+    public string HomeServerDomain { get; set; }
+    public string FullHomeServerDomain { get; set; }
+
+    private protected HttpClient _httpClient { get; set; } = new();
+    public async Task<string> ResolveHomeserverFromWellKnown(string homeserver)
+    {
+        Console.WriteLine($"Resolving homeserver: {homeserver}");
+        if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
+        if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client"))
+        {
+            Console.WriteLine($"Got successful response for client well-known...");
+            var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/client");
+            Console.WriteLine($"Response: {resp.ToString()}");
+            var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString();
+            return hs;
+        }
+        Console.WriteLine($"No client well-known...");
+        if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server"))
+        {
+            var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server");
+            var hs = resp.GetProperty("m.server").GetString();
+            return hs;
+        }
+        Console.WriteLine($"No server well-known...");
+        if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) return homeserver;
+        Console.WriteLine($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!");
+        throw new InvalidDataException($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!");
+    }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RatelimitedHttpClient.cs b/MatrixRoomUtils.Core/RatelimitedHttpClient.cs
new file mode 100644
index 0000000..f4ad9c9
--- /dev/null
+++ b/MatrixRoomUtils.Core/RatelimitedHttpClient.cs
@@ -0,0 +1,7 @@
+namespace MatrixRoomUtils;
+
+public class RatelimitedHttpClient : HttpClient
+{
+    
+    
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/LoginResponse.cs b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
index eedc970..5a7514e 100644
--- a/MatrixRoomUtils.Core/Responses/LoginResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
@@ -26,6 +26,6 @@ public class LoginResponse
     }
     public async Task<string> GetCanonicalHomeserverUrl()
     {
-        return await MatrixAccount.ResolveHomeserverFromWellKnown(HomeServer);
+        return await MatrixAuth.ResolveHomeserverFromWellKnown(HomeServer);
     }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
new file mode 100644
index 0000000..6cb439a
--- /dev/null
+++ b/MatrixRoomUtils.Core/Room.cs
@@ -0,0 +1,44 @@
+using System.Net.Http.Headers;
+using System.Net.Http.Json;
+using System.Text.Json;
+
+namespace MatrixRoomUtils;
+
+public class Room
+{
+    private readonly HttpClient _httpClient;
+    public string RoomId { get; set; }
+
+    public Room(HttpClient httpClient, string roomId)
+    {
+        _httpClient = httpClient;
+        RoomId = roomId;
+    }
+    
+    public async Task<JsonElement?> GetStateAsync(string type, string state_key="")
+    {
+        Console.WriteLine($"{RoomId}::_qry[{type}::{state_key}]");
+        var res = await _httpClient.GetAsync($"/_matrix/client/r0/rooms/{RoomId}/state/{type}/{state_key}");
+        if (!res.IsSuccessStatusCode)
+        {
+            Console.WriteLine($"{RoomId}::_qry[{type}::{state_key}]->status=={res.StatusCode}");
+            return null;
+        }
+        return await res.Content.ReadFromJsonAsync<JsonElement>();
+    }
+    public async Task<string?> GetNameAsync()
+    {   
+        Console.WriteLine($"{RoomId}::_qry_name");
+        var res = await GetStateAsync("m.room.name");
+        if (!res.HasValue)
+        {
+            Console.WriteLine($"{RoomId}::_qry_name->null");
+            return null;
+        }
+        Console.WriteLine($"{RoomId}::_qry_name->{res.Value.ToString()}");
+        var resn = res?.GetProperty("name").GetString();
+        Console.WriteLine($"Got name: {resn}");
+        return resn;
+    }
+    
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
new file mode 100644
index 0000000..45262db
--- /dev/null
+++ b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -0,0 +1,29 @@
+using MatrixRoomUtils.Authentication;
+using MatrixRoomUtils.Responses;
+
+namespace MatrixRoomUtils;
+
+public class RuntimeCache
+{
+    public static bool WasLoaded = false;
+    public static string AccessToken { get; set; }
+    public static string? CurrentHomeserver { get; set; }
+    public static AuthenticatedHomeServer CurrentHomeServer { get; set; }
+    public static Dictionary<string, UserInfo> LoginSessions { get; set; } = new();
+
+    public static Dictionary<string, HomeServerResolutionResult> HomeserverResolutionCache { get; set; } = new();
+    public static Dictionary<string, (DateTime cachedAt, ProfileResponse response)> ProfileCache { get; set; } = new();
+}
+
+
+public class UserInfo
+{
+    public ProfileResponse Profile { get; set; } = new();
+    public LoginResponse LoginResponse { get; set; }
+}
+
+public class HomeServerResolutionResult
+{
+    public string Result { get; set; }
+    public DateTime ResolutionTime { get; set; }
+}
diff --git a/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs b/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs
new file mode 100644
index 0000000..462364c
--- /dev/null
+++ b/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs
@@ -0,0 +1,35 @@
+using Blazored.LocalStorage;
+using MatrixRoomUtils.Authentication;
+using MatrixRoomUtils.Responses;
+
+namespace MatrixRoomUtils.Web.Classes;
+
+public partial class LocalStorageWrapper
+{
+    //some basic logic
+    public static async Task LoadFromLocalStorage(ILocalStorageService localStorage)
+    {
+        RuntimeCache.AccessToken = await localStorage.GetItemAsync<string>("rory.matrixroomutils.token");
+        RuntimeCache.CurrentHomeserver = await localStorage.GetItemAsync<string>("rory.matrixroomutils.current_homeserver");
+        RuntimeCache.LoginSessions = await localStorage.GetItemAsync<Dictionary<string, UserInfo>>("rory.matrixroomutils.user_cache") ?? new();
+        RuntimeCache.HomeserverResolutionCache = await localStorage.GetItemAsync<Dictionary<string, HomeServerResolutionResult>>("rory.matrixroomutils.homeserver_resolution_cache") ?? new();
+        Console.WriteLine($"[LocalStorageWrapper] Loaded {RuntimeCache.LoginSessions.Count} login sessions, {RuntimeCache.HomeserverResolutionCache.Count} homeserver resolution cache entries");
+        if (RuntimeCache.AccessToken != null && RuntimeCache.CurrentHomeserver != null)
+        {
+            Console.WriteLine($"Access token and current homeserver are not null, creating authenticated home server");
+            RuntimeCache.CurrentHomeServer = new AuthenticatedHomeServer(RuntimeCache.LoginSessions[RuntimeCache.AccessToken].LoginResponse.UserId, RuntimeCache.AccessToken, RuntimeCache.LoginSessions[RuntimeCache.AccessToken].LoginResponse.HomeServer);
+            Console.WriteLine("Created authenticated home server");
+        }
+        RuntimeCache.WasLoaded = true;
+    }
+
+    public static async Task SaveToLocalStorage(ILocalStorageService localStorage)
+    {
+        await localStorage.SetItemAsStringAsync("rory.matrixroomutils.token", RuntimeCache.AccessToken);
+        await localStorage.SetItemAsync("rory.matrixroomutils.current_homeserver", RuntimeCache.CurrentHomeserver);
+        await localStorage.SetItemAsync("rory.matrixroomutils.user_cache", RuntimeCache.LoginSessions);
+        await localStorage.SetItemAsync("rory.matrixroomutils.homeserver_resolution_cache", 
+            RuntimeCache.HomeserverResolutionCache.DistinctBy(x => x.Key)
+                .ToDictionary(x => x.Key, x => x.Value));
+    }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Classes/RuntimeStorage.cs b/MatrixRoomUtils.Web/Classes/RuntimeStorage.cs
deleted file mode 100644
index d9d626f..0000000
--- a/MatrixRoomUtils.Web/Classes/RuntimeStorage.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using Blazored.LocalStorage;
-using MatrixRoomUtils.Authentication;
-using MatrixRoomUtils.Responses;
-
-namespace MatrixRoomUtils.Web.Classes;
-
-public class RuntimeStorage
-{
-    public static bool WasLoaded = false;
-    public static UserInfo? CurrentUserInfo { get; set; }
-    public static string AccessToken { get; set; }
-    public static string? CurrentHomeserver { get; set; }
-    
-    public static List<string> AccessTokens { get; set; } = new();
-    //public static AppSettings AppSettings { get; set; } = new();
-
-    public static Dictionary<string, UserInfo> UsersCache { get; set; } = new();
-
-    public static Dictionary<string, HomeServerResolutionResult> HomeserverResolutionCache { get; set; } = new();
-
-
-    //some basic logic
-    public static async Task LoadFromLocalStorage(ILocalStorageService localStorage)
-    {
-        AccessToken = await localStorage.GetItemAsync<string>("rory.matrixroomutils.token");
-        CurrentHomeserver = await localStorage.GetItemAsync<string>("rory.matrixroomutils.current_homeserver");
-        AccessTokens = await localStorage.GetItemAsync<List<string>>("rory.matrixroomutils.tokens") ?? new();
-        UsersCache = await localStorage.GetItemAsync<Dictionary<string, UserInfo>>("rory.matrixroomutils.user_cache") ?? new();
-        HomeserverResolutionCache = await localStorage.GetItemAsync<Dictionary<string, HomeServerResolutionResult>>("rory.matrixroomutils.homeserver_resolution_cache") ?? new();
-        WasLoaded = true;
-    }
-
-    public static async Task SaveToLocalStorage(ILocalStorageService localStorage)
-    {
-        await localStorage.SetItemAsStringAsync("rory.matrixroomutils.token", AccessToken);
-        await localStorage.SetItemAsync("rory.matrixroomutils.current_homeserver", CurrentHomeserver);
-        await localStorage.SetItemAsync("rory.matrixroomutils.tokens", AccessTokens);
-        await localStorage.SetItemAsync("rory.matrixroomutils.user_cache", UsersCache);
-        await localStorage.SetItemAsync("rory.matrixroomutils.homeserver_resolution_cache", 
-            HomeserverResolutionCache.DistinctBy(x => x.Key)
-                .ToDictionary(x => x.Key, x => x.Value));
-    }
-}
-
-public class UserInfo
-{
-    public ProfileResponse Profile { get; set; } = new();
-    public LoginResponse LoginResponse { get; set; }
-}
-
-public class HomeServerResolutionResult
-{
-    public string Result { get; set; }
-    public DateTime ResolutionTime { get; set; }
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/DataExportPage.razor b/MatrixRoomUtils.Web/Pages/DataExportPage.razor
index a7c6f6b..f9a4c37 100644
--- a/MatrixRoomUtils.Web/Pages/DataExportPage.razor
+++ b/MatrixRoomUtils.Web/Pages/DataExportPage.razor
@@ -17,13 +17,13 @@
 <hr/>
 @if (_isLoaded)
 {
-@foreach (var (token, user) in RuntimeStorage.UsersCache)
+@foreach (var (token, user) in RuntimeCache.LoginSessions)
 {
     <IndexUserItem User="@user" Token="@token"/>
     <pre>
 @user.LoginResponse.UserId[1..].Split(":")[0]\auth\access_token=@token
 @user.LoginResponse.UserId[1..].Split(":")[0]\auth\device_id=@user.LoginResponse.DeviceId
-@user.LoginResponse.UserId[1..].Split(":")[0]\auth\home_server=@(RuntimeStorage.HomeserverResolutionCache.ContainsKey(user.LoginResponse.HomeServer) ? RuntimeStorage.HomeserverResolutionCache[user.LoginResponse.HomeServer].Result : "loading...")
+@user.LoginResponse.UserId[1..].Split(":")[0]\auth\home_server=@(RuntimeCache.HomeserverResolutionCache.ContainsKey(user.LoginResponse.HomeServer) ? RuntimeCache.HomeserverResolutionCache[user.LoginResponse.HomeServer].Result : "loading...")
 @user.LoginResponse.UserId[1..].Split(":")[0]\auth\user_id=@@@user.LoginResponse.UserId
 @user.LoginResponse.UserId[1..].Split(":")[0]\user\automatically_share_keys_with_trusted_users=true
 @user.LoginResponse.UserId[1..].Split(":")[0]\user\muted_tags=global
@@ -46,29 +46,25 @@ else
     protected override async Task OnInitializedAsync()
     {
         await base.OnInitializedAsync();
-        Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count);
-        if (!RuntimeStorage.WasLoaded)
+        if (!RuntimeCache.WasLoaded)
         {
-            Console.WriteLine("[INDEX] !!! LOCALSTORAGE WAS NOT LOADED !!!");
-            await RuntimeStorage.LoadFromLocalStorage(LocalStorage);
+            await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
 
-            Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count);
-
-            var homeservers = RuntimeStorage.UsersCache.Values.Select(x => x.LoginResponse.HomeServer).Distinct();
+            var homeservers = RuntimeCache.LoginSessions.Values.Select(x => x.LoginResponse.HomeServer).Distinct();
             totalHomeservers = homeservers.Count();
             StateHasChanged();
             foreach (var hs in homeservers)
             {
-                if (RuntimeStorage.HomeserverResolutionCache.ContainsKey(hs)) continue;
-                var resolvedHomeserver = await MatrixAccount.ResolveHomeserverFromWellKnown(hs);
+                if (LocalStorageWrapper.HomeserverResolutionCache.ContainsKey(hs)) continue;
+                var resolvedHomeserver = await MatrixAuth.ResolveHomeserverFromWellKnown(hs);
 
-                if (RuntimeStorage.HomeserverResolutionCache.ContainsKey(hs))
-                    RuntimeStorage.HomeserverResolutionCache.Remove(hs);
-                RuntimeStorage.HomeserverResolutionCache.Add(hs, new(){Result = resolvedHomeserver, ResolutionTime = DateTime.Now});
-                await RuntimeStorage.SaveToLocalStorage(LocalStorage);
+                if (LocalStorageWrapper.HomeserverResolutionCache.ContainsKey(hs))
+                    LocalStorageWrapper.HomeserverResolutionCache.Remove(hs);
+                LocalStorageWrapper.HomeserverResolutionCache.Add(hs, new() { Result = resolvedHomeserver, ResolutionTime = DateTime.Now });
+                await LocalStorageWrapper.SaveToLocalStorage(LocalStorage);
 
                 Console.WriteLine("Saved to local storage:");
-                Console.WriteLine(JsonSerializer.Serialize(RuntimeStorage.HomeserverResolutionCache, new JsonSerializerOptions()
+                Console.WriteLine(JsonSerializer.Serialize(LocalStorageWrapper.HomeserverResolutionCache, new JsonSerializerOptions()
                 {
                     WriteIndented = true
                 }));
diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor
index 7e9facf..ea9404d 100644
--- a/MatrixRoomUtils.Web/Pages/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Index.razor
@@ -14,19 +14,19 @@ Small collection of tools to do not-so-everyday things.
 <h5>Signed in accounts - <a href="/Login">Add new account</a> or <a href="/ImportUsers">Import from TSV</a></h5>
 <hr/>
 @{
-    Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count);
-    if (!RuntimeStorage.WasLoaded)
+    Console.WriteLine("Users in cache: " + LocalStorageWrapper.LoginSessions.Count);
+    if (!LocalStorageWrapper.WasLoaded)
     {
         Console.WriteLine("[INDEX] !!! LOCALSTORAGE WAS NOT LOADED !!!");
-        RuntimeStorage.LoadFromLocalStorage(LocalStorage).GetAwaiter().OnCompleted(() =>
+        LocalStorageWrapper.LoadFromLocalStorage(LocalStorage).GetAwaiter().OnCompleted(() =>
         {
-            Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count);
+            Console.WriteLine("Users in cache: " + LocalStorageWrapper.LoginSessions.Count);
             StateHasChanged();
         });
     }
 }
 <form>
-    @foreach (var (token, user) in RuntimeStorage.UsersCache)
+    @foreach (var (token, user) in LocalStorageWrapper.LoginSessions)
     {
         <IndexUserItem User="@user" Token="@token"/>
     }
diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor
index d193f95..aead5e8 100644
--- a/MatrixRoomUtils.Web/Pages/LoginPage.razor
+++ b/MatrixRoomUtils.Web/Pages/LoginPage.razor
@@ -25,22 +25,22 @@
     string password = "";
     async Task Login()
     {
-        var result = await MatrixAccount.Login(homeserver, username, password);
+        var result = await MatrixAuth.Login(homeserver, username, password);
         Console.WriteLine($"Obtained access token for {result.UserId}!");
         
-        RuntimeStorage.AccessToken = result.AccessToken;
+        LocalStorageWrapper.AccessToken = result.AccessToken;
 
         var userinfo = new UserInfo()
         {
             LoginResponse = result
         };
-        userinfo.Profile = await MatrixAccount.GetProfile(result.HomeServer, result.UserId);
+        userinfo.Profile = await MatrixAuth.GetProfile(result.HomeServer, result.UserId);
 
-        RuntimeStorage.UsersCache.Add(result.AccessToken, userinfo);
-        RuntimeStorage.CurrentHomeserver = await MatrixAccount.ResolveHomeserverFromWellKnown(result.HomeServer);
+        LocalStorageWrapper.LoginSessions.Add(result.AccessToken, userinfo);
+        LocalStorageWrapper.CurrentHomeserver = await MatrixAuth.ResolveHomeserverFromWellKnown(result.HomeServer);
         
 
-        await RuntimeStorage.SaveToLocalStorage(LocalStorage);
+        await LocalStorageWrapper.SaveToLocalStorage(LocalStorage);
 
     }
 }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor b/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor
index cedaf32..6c276b6 100644
--- a/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor
+++ b/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor
@@ -195,9 +195,9 @@ else
 
     protected override async Task OnInitializedAsync()
     {
-        if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage);
+        if (!LocalStorageWrapper.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
         await base.OnInitializedAsync();
-        if(RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null)
+        if(LocalStorageWrapper.AccessToken == null || LocalStorageWrapper.CurrentHomeserver == null)
         {
             NavigationManager.NavigateTo("/Login");
             return;
@@ -210,8 +210,8 @@ else
     private async Task LoadStatesAsync()
     {
         using var client = new HttpClient();
-        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken);
-        var response = await client.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state");
+        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+        var response = await client.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state");
         var content = await response.Content.ReadAsStringAsync();
         // Console.WriteLine(JsonSerializer.Deserialize<object>(content).ToJson());
         var stateEvents = JsonSerializer.Deserialize<List<StateEvent>>(content);
diff --git a/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor b/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor
index 9be8314..032d150 100644
--- a/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor
+++ b/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor
@@ -26,12 +26,12 @@ else
     }
     foreach (var s in PolicyRoomList)
     {
-        <a href="@(NavigationManager.Uri + "/" + s.RoomId.Replace('.', '~'))">@s.Name</a>
+        <a href="@(NavigationManager.Uri + "/" + s.RoomId.Replace('.', '~'))">[@s.Shortcode] @s.Name (@s.RoomId)</a>
         <br/>
     }
-    <div style="margin-bottom: 4em;"></div>
 }
 
+<div style="margin-bottom: 4em;"></div>
 <LogView></LogView>
 
 @code {
@@ -47,9 +47,9 @@ else
 
     protected override async Task OnInitializedAsync()
     {
-        if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage);
+        if (!LocalStorageWrapper.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
         await base.OnInitializedAsync();
-        if (RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null)
+        if (LocalStorageWrapper.AccessToken == null || LocalStorageWrapper.CurrentHomeserver == null)
         {
             NavigationManager.NavigateTo("/Login");
             return;
@@ -60,13 +60,31 @@ else
 
     private async Task EnumeratePolicyRooms()
     {
+        var xxxrooms = await LocalStorageWrapper.CurrentHomeServer.GetJoinedRooms();
+        totalRoomCount = xxxrooms.Count;
+        StateHasChanged();
+
+        var xxxsemaphore = new SemaphoreSlim(256);
+        var xxxtasks = new List<Task<PolicyRoomInfo?>>();
+        foreach (var room in xxxrooms)
+        {
+            xxxtasks.Add(GetPolicyRoomInfo(room.RoomId, xxxsemaphore));
+        }
+        var xxxresults = await Task.WhenAll(xxxtasks);
+        PolicyRoomList.AddRange(xxxresults.Where(x => x != null).Select(x => x.Value));
+
+        Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}");
+        return;
+        
         using HttpClient wc = new();
-        wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken);
+        wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
 
+        
+        
     //get room list
     //temporary hack until rooms get enumerated...
         string[] rooms = { "!fTjMjIzNKEsFlUIiru:neko.dev" };
-        var _rooms = await wc.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/v3/joined_rooms");
+        var _rooms = await wc.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/v3/joined_rooms");
         Console.WriteLine($"Got {_rooms.StatusCode}...");
         if (!_rooms.IsSuccessStatusCode)
         {
@@ -82,15 +100,15 @@ else
         totalRoomCount = rooms.Length;
         StateHasChanged();
 
-        var semaphore = new SemaphoreSlim(128);
+        var semaphore = new SemaphoreSlim(256);
         var tasks = new List<Task<PolicyRoomInfo?>>();
         foreach (string room in rooms)
         {
             tasks.Add(GetPolicyRoomInfo(room, semaphore));
         }
         var results = await Task.WhenAll(tasks);
-        PolicyRoomList.AddRange(results.Where(x => x != null).Select(x=>x.Value));
-        
+        PolicyRoomList.AddRange(results.Where(x => x != null).Select(x => x.Value));
+
 
     //print to console
         Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}");
@@ -101,16 +119,40 @@ else
         try
         {
             await semaphore.WaitAsync();
+            PolicyRoomInfo roomInfo = new()
+            {
+                RoomId = room
+            };
             using HttpClient wc = new();
-            wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken);
-            var sk = await wc.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/v3/rooms/{room}/state/org.matrix.mjolnir.shortcode");
+            wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+            var sk = await wc.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/v3/rooms/{room}/state/org.matrix.mjolnir.shortcode");
             if (sk.IsSuccessStatusCode)
             {
                 var sko = await sk.Content.ReadFromJsonAsync<JsonElement>();
                 if (sko.TryGetProperty("shortcode", out JsonElement shortcode))
                 {
                     Console.WriteLine($"Room {room} has a shortcode: {shortcode.GetString()}!");
-                    return new PolicyRoomInfo() { Name = room, Shortcode = shortcode.GetString(), RoomId = room };
+                    roomInfo.Shortcode = shortcode.GetString();
+                    // sk = await wc.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/v3/rooms/{room}/state/m.room.name");
+                    // if (sk.IsSuccessStatusCode)
+                    // {
+                    //     Console.WriteLine($"Got content: {await sk.Content.ReadAsStringAsync()}");
+                    //     sko = await sk.Content.ReadFromJsonAsync<JsonElement>();
+                    //     if (sko.TryGetProperty("name", out JsonElement roomname))
+                    //     {
+                    //         Console.WriteLine($"Room {room} has a name: {roomname.GetString()}!");
+                    //         roomInfo.Name = roomname.GetString();
+                    //     }
+                    //     else Console.WriteLine("No record found...");
+                    // }
+                    // else if (sk.StatusCode == System.Net.HttpStatusCode.NotFound)
+                    // {
+                    // }
+                    // else Console.WriteLine($"Got failure while checking {room}: {sk.StatusCode} ({await sk.Content.ReadAsStringAsync()})...");
+                    var r = await LocalStorageWrapper.CurrentHomeServer.GetRoom(room);
+                    roomInfo.Shortcode = (await r.GetStateAsync("org.matrix.mjolnir.shortcode")).Value.GetProperty("shortcode").GetString();
+                    roomInfo.Name = await r.GetNameAsync();
+                    return roomInfo;
                 }
                 else Console.WriteLine("No record found...");
             }
@@ -118,6 +160,7 @@ else
             {
             }
             else Console.WriteLine($"Got failure while checking {room}: {sk.StatusCode} ({await sk.Content.ReadAsStringAsync()})...");
+            
             return null;
         }
         finally
@@ -127,11 +170,11 @@ else
             semaphore.Release();
         }
     }
-    
+
     public struct PolicyRoomInfo
     {
         public string RoomId { get; set; }
-        public string Shortcode { get; set; }
-        public string Name { get; set; }
+        public string? Shortcode { get; set; }
+        public string? Name { get; set; }
     }
-} 
\ No newline at end of file
+    } 
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/RoomStateEditorPage.razor b/MatrixRoomUtils.Web/Pages/RoomStateEditorPage.razor
new file mode 100644
index 0000000..6ad99d9
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/RoomStateEditorPage.razor
@@ -0,0 +1,177 @@
+@page "/RoomStateViewer/{RoomId}/Edit"
+@using MatrixRoomUtils.Authentication
+@using MatrixRoomUtils.Web.Classes
+@using Blazored.LocalStorage
+@using System.Net.Http.Headers
+@using System.Text.Json
+@using System.Xml.Schema
+@using MatrixRoomUtils.Extensions
+@using MatrixRoomUtils.StateEventTypes
+@using MatrixRoomUtils.Web.Shared.IndexComponents
+@using Microsoft.Win32.SafeHandles
+@inject ILocalStorageService LocalStorage
+@inject NavigationManager NavigationManager
+<h3>Room state editor</h3>
+<p>Room ID: @RoomId</p>
+
+<p>@status</p>
+
+<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events
+<br/>
+<InputSelect @bind-Value="shownStateKey">
+    <option value="">-- State key --</option>
+    @foreach (var stateEvent in FilteredEvents.Where(x => x.state_key != "").Select(x => x.state_key).Distinct().OrderBy(x => x))
+    {
+        <option value="@stateEvent">@stateEvent</option>
+        Console.WriteLine(stateEvent);
+    }
+</InputSelect>
+<br/>
+<InputSelect @bind-Value="shownType">
+    <option value="">-- Type --</option>
+    @foreach (var stateEvent in FilteredEvents.Where(x => x.state_key != shownStateKey).Select(x => x.type).Distinct().OrderBy(x => x))
+    {
+        <option value="@stateEvent">@stateEvent</option>
+    }
+</InputSelect>
+<br/>
+
+<textarea @bind="shownEventJson" style="width: 100%; height: fit-content;"></textarea>
+
+<LogView></LogView>
+
+@code {
+    //get room list
+    // - sync withroom list filter
+    // type = support.feline.msc3784
+    //support.feline.policy.lists.msc.v1
+
+    [Parameter]
+    public string? RoomId { get; set; }
+
+    public List<StateEvent> FilteredEvents { get; set; } = new();
+    public List<StateEvent> Events { get; set; } = new();
+    public string status = "";
+
+    protected override async Task OnInitializedAsync()
+    {
+        if (!LocalStorageWrapper.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+        await base.OnInitializedAsync();
+        if (LocalStorageWrapper.AccessToken == null || LocalStorageWrapper.CurrentHomeserver == null)
+        {
+            NavigationManager.NavigateTo("/Login");
+            return;
+        }
+        RoomId = RoomId.Replace('~', '.');
+        await LoadStatesAsync();
+        Console.WriteLine("Policy list editor initialized!");
+    }
+
+    private DateTime _lastUpdate = DateTime.Now;
+
+    private async Task LoadStatesAsync()
+    {
+        int StateLoaded = 0;
+        using var client = new HttpClient();
+        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+        var response = await client.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state");
+    // var response = await client.GetAsync($"http://localhost:5117/matrix-hq-state.json");
+    //var _events = await response.Content.ReadFromJsonAsync<Queue<StateEventStruct>>();
+        var _data = await response.Content.ReadAsStreamAsync();
+        var __events = JsonSerializer.DeserializeAsyncEnumerable<StateEvent>(_data);
+        await foreach (var _ev in __events)
+        {
+            var e = new StateEvent()
+            {
+                type = _ev.type,
+                state_key = _ev.state_key,
+                origin_server_ts = _ev.origin_server_ts,
+                content = _ev.content
+            };
+            Events.Add(e);
+            if (string.IsNullOrEmpty(e.state_key))
+            {
+                FilteredEvents.Add(e);
+            }
+            StateLoaded++;
+            if ((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)
+            {
+                _lastUpdate = DateTime.Now;
+                status = $"Loaded {StateLoaded} state events";
+                StateHasChanged();
+                await Task.Delay(0);
+            }
+        }
+
+        StateHasChanged();
+    }
+
+    private async Task RebuildFilteredData()
+    {
+        status = "Rebuilding filtered data...";
+        StateHasChanged();
+        await Task.Delay(1);
+        var _FilteredEvents = Events;
+        if (!ShowMembershipEvents)
+            _FilteredEvents = _FilteredEvents.Where(x => x.type != "m.room.member").ToList();
+
+        status = "Done, rerendering!";
+        StateHasChanged();
+        await Task.Delay(1);
+        FilteredEvents = _FilteredEvents;
+        
+        if(_shownType != null)
+            shownEventJson = _FilteredEvents.Where(x => x.type == _shownType).First().content.ToJson(indent: true, ignoreNull: true);
+        
+        StateHasChanged();
+    }
+
+
+    public struct PreRenderedStateEvent
+    {
+        public string content { get; set; }
+        public long origin_server_ts { get; set; }
+        public string state_key { get; set; }
+        public string type { get; set; }
+    // public string sender { get; set; }
+    // public string event_id { get; set; }
+    // public string user_id { get; set; }
+    // public string replaces_state { get; set; }
+    }
+
+    public bool ShowMembershipEvents
+    {
+        get => _showMembershipEvents;
+        set
+        {
+            _showMembershipEvents = value;
+            RebuildFilteredData();
+        }
+    }
+
+    private bool _showMembershipEvents;
+    private string _shownStateKey;
+    private string _shownType;
+
+    private string shownStateKey
+    {
+        get => _shownStateKey;
+        set
+        {
+            _shownStateKey = value;
+            RebuildFilteredData();
+        }
+    }
+
+    private string shownType
+    {
+        get => _shownType;
+        set
+        {
+            _shownType = value;
+            RebuildFilteredData();
+        }
+    }
+
+    private string shownEventJson { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor b/MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor
index 5a55b01..b13d069 100644
--- a/MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor
+++ b/MatrixRoomUtils.Web/Pages/RoomStateRoomList.razor
@@ -9,7 +9,7 @@
 @using MatrixRoomUtils.StateEventTypes
 @inject ILocalStorageService LocalStorage
 @inject NavigationManager NavigationManager
-<h3>Policy list editor</h3>
+<h3>Room state viewer</h3>
 
 <h5>Room list</h5>
 <hr/>
@@ -26,7 +26,7 @@ else
     }
     foreach (var s in PolicyRoomList)
     {
-        <a href="@(NavigationManager.Uri + "/" + s.RoomId.Replace('.', '~'))">@s.Name</a>
+        <a href="@(NavigationManager.Uri + "/" + s.RoomId.Replace('.', '~'))">@s.Name (@s.RoomId)</a>
         <br/>
     }
     <div style="margin-bottom: 4em;"></div>
@@ -43,9 +43,9 @@ else
 
     protected override async Task OnInitializedAsync()
     {
-        if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage);
+        if (!LocalStorageWrapper.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
         await base.OnInitializedAsync();
-        if (RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null)
+        if (LocalStorageWrapper.AccessToken == null || LocalStorageWrapper.CurrentHomeserver == null)
         {
             NavigationManager.NavigateTo("/Login");
             return;
@@ -57,12 +57,12 @@ else
     private async Task EnumeratePolicyRooms()
     {
         using HttpClient wc = new();
-        wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken);
+        wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
 
     //get room list
     //temporary hack until rooms get enumerated...
         string[] rooms = { "!fTjMjIzNKEsFlUIiru:neko.dev" };
-        var _rooms = await wc.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/v3/joined_rooms");
+        var _rooms = await wc.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/v3/joined_rooms");
         Console.WriteLine($"Got {_rooms.StatusCode}...");
         if (!_rooms.IsSuccessStatusCode)
         {
@@ -78,26 +78,59 @@ else
         totalRoomCount = rooms.Length;
         StateHasChanged();
 
-        // var semaphore = new SemaphoreSlim(128);
-        // var tasks = new List<Task<PolicyRoomInfo?>>();
-        // foreach (string room in rooms)
-        // {
-        //     tasks.Add(GetPolicyRoomInfo(room, semaphore));
-        // }
-        // var results = await Task.WhenAll(tasks);
-        // PolicyRoomList.AddRange(results.Where(x => x != null).Select(x=>x.Value));
-        PolicyRoomList.AddRange(rooms.Select(x=>new PolicyRoomInfo() { Name = x, RoomId = x, Shortcode = "N/A" }));
+        var semaphore = new SemaphoreSlim(128);
+        var tasks = new List<Task<PolicyRoomInfo?>>();
+        foreach (string room in rooms)
+        {
+            tasks.Add(GetPolicyRoomInfo(room, semaphore));
+        }
+        var results = await Task.WhenAll(tasks);
+        PolicyRoomList.AddRange(results.Where(x => x != null).Select(x=>x.Value));
         
+        StateHasChanged();
+    }
 
-    //print to console
-        Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}");
+    private async Task<PolicyRoomInfo?> GetPolicyRoomInfo(string room, SemaphoreSlim semaphore)
+    {
+        try
+        {
+            await semaphore.WaitAsync();
+            var roomInfo = new PolicyRoomInfo()
+            {
+                RoomId = room
+            };
+            using HttpClient wc = new();
+            wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+            var sk = await wc.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/v3/rooms/{room}/state/m.room.name");
+            if (sk.IsSuccessStatusCode)
+            {
+                Console.WriteLine($"Got content: {await sk.Content.ReadAsStringAsync()}");
+                var sko = await sk.Content.ReadFromJsonAsync<JsonElement>();
+                if (sko.TryGetProperty("name", out JsonElement shortcode))
+                {
+                    Console.WriteLine($"Room {room} has a name: {shortcode.GetString()}!");
+                    roomInfo.Name = shortcode.GetString();
+                }
+                else Console.WriteLine("No record found...");
+            }
+            else if (sk.StatusCode == System.Net.HttpStatusCode.NotFound)
+            {
+            }
+            else Console.WriteLine($"Got failure while checking {room}: {sk.StatusCode} ({await sk.Content.ReadAsStringAsync()})...");
+            return roomInfo;
+        }
+        finally
+        {
+            checkedRoomCount++;
+            StateHasChanged();
+            semaphore.Release();
+        }
     }
-    
-    
+
+
     public struct PolicyRoomInfo
     {
         public string RoomId { get; set; }
-        public string Shortcode { get; set; }
         public string Name { get; set; }
     }
 } 
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor b/MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor
index ffd17d4..8c33d89 100644
--- a/MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor
+++ b/MatrixRoomUtils.Web/Pages/RoomStateViewerPage.razor
@@ -80,9 +80,9 @@
 
     protected override async Task OnInitializedAsync()
     {
-        if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage);
+        if (!LocalStorageWrapper.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
         await base.OnInitializedAsync();
-        if (RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null)
+        if (LocalStorageWrapper.AccessToken == null || LocalStorageWrapper.CurrentHomeserver == null)
         {
             NavigationManager.NavigateTo("/Login");
             return;
@@ -97,8 +97,8 @@
     {
         int StateLoaded = 0;
         using var client = new HttpClient();
-        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken);
-    var response = await client.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state");
+        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", LocalStorageWrapper.AccessToken);
+    var response = await client.GetAsync($"{LocalStorageWrapper.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state");
         // var response = await client.GetAsync($"http://localhost:5117/matrix-hq-state.json");
     //var _events = await response.Content.ReadFromJsonAsync<Queue<StateEventStruct>>();
         var _data = await response.Content.ReadAsStreamAsync();
diff --git a/MatrixRoomUtils.Web/Pages/UserImportPage.razor b/MatrixRoomUtils.Web/Pages/UserImportPage.razor
index ca0a213..612b3ce 100644
--- a/MatrixRoomUtils.Web/Pages/UserImportPage.razor
+++ b/MatrixRoomUtils.Web/Pages/UserImportPage.razor
@@ -15,7 +15,7 @@
 <table border="1">
     @foreach (var (homeserver, username, password) in records)
     {
-        <tr style="background-color: @(RuntimeStorage.UsersCache.Any(x => x.Value.LoginResponse.UserId == $"@{username}:{homeserver}") ? "green" : "unset")">
+        <tr style="background-color: @(LocalStorageWrapper.LoginSessions.Any(x => x.Value.LoginResponse.UserId == $"@{username}:{homeserver}") ? "green" : "unset")">
             <td style="border-width: 1px;">@username</td>
             <td style="border-width: 1px;">@homeserver</td>
             <td style="border-width: 1px;">@password.Length chars</td>
@@ -33,23 +33,21 @@
     {
         foreach (var (homeserver, username, password) in records)
         {
-            if(RuntimeStorage.UsersCache.Any(x => x.Value.LoginResponse.UserId == $"@{username}:{homeserver}")) continue;
-            var result = await MatrixAccount.Login(homeserver, username, password);
+            if(LocalStorageWrapper.LoginSessions.Any(x => x.Value.LoginResponse.UserId == $"@{username}:{homeserver}")) continue;
+            var result = await MatrixAuth.Login(homeserver, username, password);
             Console.WriteLine($"Obtained access token for {result.UserId}!");
 
-            RuntimeStorage.AccessToken = result.AccessToken;
-
             var userinfo = new UserInfo()
             {
                 LoginResponse = result
             };
-            userinfo.Profile = await MatrixAccount.GetProfile(result.HomeServer, result.UserId);
+            userinfo.Profile = await MatrixAuth.GetProfile(result.HomeServer, result.UserId);
 
-            RuntimeStorage.UsersCache.Add(result.AccessToken, userinfo);
+            LocalStorageWrapper.LoginSessions.Add(result.AccessToken, userinfo);
             StateHasChanged();
         }
         
-        await RuntimeStorage.SaveToLocalStorage(LocalStorage);
+        await LocalStorageWrapper.SaveToLocalStorage(LocalStorage);
     }
 
     private async Task FileChanged(InputFileChangeEventArgs obj)
diff --git a/MatrixRoomUtils.Web/Shared/IndexComponents/IndexUserItem.razor b/MatrixRoomUtils.Web/Shared/IndexComponents/IndexUserItem.razor
index cc6f9f6..33054e5 100644
--- a/MatrixRoomUtils.Web/Shared/IndexComponents/IndexUserItem.razor
+++ b/MatrixRoomUtils.Web/Shared/IndexComponents/IndexUserItem.razor
@@ -9,7 +9,7 @@
 
 <div style="margin-bottom: 1em;">
     <img style="border-radius: 50%; height: 3em; width: 3em;" src="@_avatarUrl"/>
-    <span style="margin-left: 1em;"><input type="radio" name="csa" checked="@(RuntimeStorage.AccessToken == Token)" onclick="@SetCurrent" style="text-decoration-line: unset;"/> <b>@User.Profile.DisplayName</b> on <b>@User.LoginResponse.HomeServer</b></span>
+    <span style="margin-left: 1em;"><input type="radio" name="csa" checked="@(LocalStorageWrapper.AccessToken == Token)" onclick="@SetCurrent" style="text-decoration-line: unset;"/> <b>@User.Profile.DisplayName</b> on <b>@User.LoginResponse.HomeServer</b></span>
     <a href="#" onclick="@RemoveUser">Remove</a>
 </div>
 
@@ -32,17 +32,18 @@
 
     private async Task RemoveUser()
     {
-        RuntimeStorage.UsersCache.Remove(Token);
-        await RuntimeStorage.SaveToLocalStorage(LocalStorage);
+        LocalStorageWrapper.LoginSessions.Remove(Token);
+        await LocalStorageWrapper.SaveToLocalStorage(LocalStorage);
         _removed = true;
         
         StateHasChanged();
     }
     private async Task SetCurrent()
     {
-        RuntimeStorage.AccessToken = Token;
-        RuntimeStorage.CurrentHomeserver = await MatrixAccount.ResolveHomeserverFromWellKnown(RuntimeStorage.UsersCache[Token].LoginResponse.HomeServer);
-        await RuntimeStorage.SaveToLocalStorage(LocalStorage);
+        LocalStorageWrapper.AccessToken = Token;
+        LocalStorageWrapper.CurrentHomeserver = await MatrixAuth.ResolveHomeserverFromWellKnown(LocalStorageWrapper.LoginSessions[Token].LoginResponse.HomeServer);
+        await LocalStorageWrapper.SaveToLocalStorage(LocalStorage);
+        await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
         
         StateHasChanged();
     }
diff --git a/MatrixRoomUtils.Web/Shared/MainLayout.razor b/MatrixRoomUtils.Web/Shared/MainLayout.razor
index e0c8260..ae7c3f5 100644
--- a/MatrixRoomUtils.Web/Shared/MainLayout.razor
+++ b/MatrixRoomUtils.Web/Shared/MainLayout.razor
@@ -25,9 +25,9 @@
 
     protected override async Task OnInitializedAsync()
     {
-        if (!RuntimeStorage.WasLoaded)
+        if (!LocalStorageWrapper.WasLoaded)
         {
-            await RuntimeStorage.LoadFromLocalStorage(LocalStorage);
+            await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
             Console.WriteLine("Loaded from local storage");
             StateHasChanged();
         }