diff --git a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
index f446bf3..925266b 100644
--- a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
+++ b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
@@ -1,22 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.8" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\MatrixUtils.Web\MatrixUtils.Web.csproj" />
+ <ProjectReference Include="..\MatrixUtils.Web\MatrixUtils.Web.csproj"/>
</ItemGroup>
<ItemGroup>
- <Folder Include="Controllers" />
+ <Folder Include="Controllers"/>
</ItemGroup>
diff --git a/MatrixUtils.Web/App.razor b/MatrixUtils.Web/App.razor
index a8cf817..7e8e1c3 100644
--- a/MatrixUtils.Web/App.razor
+++ b/MatrixUtils.Web/App.razor
@@ -1,4 +1,5 @@
-<Router AppAssembly="@typeof(App).Assembly">
+@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
+<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
@@ -10,3 +11,9 @@
</LayoutView>
</NotFound>
</Router>
+
+@code {
+
+ public static WebAssemblyHost Host { get; set; } = null!;
+
+}
diff --git a/MatrixUtils.Web/Classes/LocalStorageProviderService.cs b/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
index 0e99cd4..ddf3eed 100644
--- a/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
+++ b/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
@@ -3,26 +3,20 @@ using LibMatrix.Interfaces.Services;
namespace MatrixUtils.Web.Classes;
-public class LocalStorageProviderService : IStorageProvider {
- private readonly ILocalStorageService _localStorageService;
-
- public LocalStorageProviderService(ILocalStorageService localStorageService) {
- _localStorageService = localStorageService;
- }
-
+public class LocalStorageProviderService(ILocalStorageService localStorageService) : IStorageProvider {
Task IStorageProvider.SaveAllChildrenAsync<T>(string key, T value) {
throw new NotImplementedException();
}
Task<T?> IStorageProvider.LoadAllChildrenAsync<T>(string key) where T : default => throw new NotImplementedException();
- async Task IStorageProvider.SaveObjectAsync<T>(string key, T value) => await _localStorageService.SetItemAsync(key, value);
+ async Task IStorageProvider.SaveObjectAsync<T>(string key, T value) => await localStorageService.SetItemAsync(key, value);
- async Task<T?> IStorageProvider.LoadObjectAsync<T>(string key) where T : default => await _localStorageService.GetItemAsync<T>(key);
+ async Task<T?> IStorageProvider.LoadObjectAsync<T>(string key) where T : default => await localStorageService.GetItemAsync<T>(key);
- async Task<bool> IStorageProvider.ObjectExistsAsync(string key) => await _localStorageService.ContainKeyAsync(key);
+ async Task<bool> IStorageProvider.ObjectExistsAsync(string key) => await localStorageService.ContainKeyAsync(key);
- async Task<IEnumerable<string>> IStorageProvider.GetAllKeysAsync() => (await _localStorageService.KeysAsync()).ToList();
+ async Task<IEnumerable<string>> IStorageProvider.GetAllKeysAsync() => (await localStorageService.KeysAsync()).ToList();
- async Task IStorageProvider.DeleteObjectAsync(string key) => await _localStorageService.RemoveItemAsync(key);
+ async Task IStorageProvider.DeleteObjectAsync(string key) => await localStorageService.RemoveItemAsync(key);
}
diff --git a/MatrixUtils.Web/Classes/RmuSessionStore.cs b/MatrixUtils.Web/Classes/RmuSessionStore.cs
index 9df8837..1611b83 100644
--- a/MatrixUtils.Web/Classes/RmuSessionStore.cs
+++ b/MatrixUtils.Web/Classes/RmuSessionStore.cs
@@ -26,6 +26,11 @@ public class RmuSessionStore(
public async Task<SessionInfo?> GetSession(string sessionId) {
await LoadStorage();
+ if (string.IsNullOrEmpty(sessionId)) {
+ logger.LogWarning("No session ID provided.");
+ return null;
+ }
+
if (SessionCache.TryGetValue(sessionId, out var cachedSession))
return cachedSession;
@@ -39,6 +44,11 @@ public class RmuSessionStore(
if (CurrentSession is not null) return CurrentSession;
var currentSessionId = await storageService.DataStorageProvider!.LoadObjectAsync<string>("rmu.session");
+ if (currentSessionId == null) {
+ if (log) logger.LogWarning("No current session ID found in storage.");
+ return null;
+ }
+
return await GetSession(currentSessionId);
}
@@ -52,25 +62,31 @@ public class RmuSessionStore(
SessionId = sessionId
};
+ await SaveStorage();
if (CurrentSession == null) await SetCurrentSession(sessionId);
- else await SaveStorage();
return sessionId;
}
public async Task RemoveSession(string sessionId) {
await LoadStorage();
- logger.LogTrace("Removing session {sessionId}.", sessionId);
- var tokens = await GetAllSessions();
- if (tokens == null) {
+ if (SessionCache.Count == 0) {
+ logger.LogWarning("No sessions found.");
return;
}
+ logger.LogTrace("Removing session {sessionId}.", sessionId);
+
if ((await GetCurrentSession())?.SessionId == sessionId)
- await SetCurrentSession(tokens.First(x => x.Key != sessionId).Key);
+ await SetCurrentSession(SessionCache.FirstOrDefault(x => x.Key != sessionId).Key);
- if (tokens.Remove(sessionId))
- await SaveStorage();
+ if (SessionCache.Remove(sessionId)) {
+ logger.LogInformation("RemoveSession: Removed session {sessionId}.", sessionId);
+ logger.LogInformation("RemoveSession: Remaining sessions: {sessionIds}.", string.Join(", ", SessionCache.Keys));
+ await SaveStorage(log: true);
+ }
+ else
+ logger.LogWarning("RemoveSession: Session {sessionId} not found.", sessionId);
}
public async Task SetCurrentSession(string? sessionId) {
@@ -134,6 +150,53 @@ public class RmuSessionStore(
}
}
+ public async IAsyncEnumerable<AuthenticatedHomeserverGeneric> TryGetAllHomeservers(bool log = true, bool ignoreFailures = true) {
+ await LoadStorage();
+ if (log) logger.LogTrace("Getting all homeservers.");
+ var tasks = SessionCache.Values.Select(async session => {
+ if (ignoreFailures && session.Auth.LastFailureReason != null && session.Auth.LastFailureReason != UserAuth.FailureReason.None) {
+ if (log) logger.LogTrace("Skipping session {sessionId} due to previous failure: {reason}", session.SessionId, session.Auth.LastFailureReason);
+ return null;
+ }
+
+ try {
+ var hs = await GetHomeserver(session.SessionId, log: false);
+ if (session.Auth.LastFailureReason != null) {
+ SessionCache[session.SessionId].Auth.LastFailureReason = null;
+ await SaveStorage();
+ }
+
+ return hs;
+ }
+ catch (Exception e) {
+ logger.LogError("TryGetAllHomeservers: Failed to get homeserver for {userId} via {homeserver}: {ex}", session.Auth.UserId, session.Auth.Homeserver, e);
+ var reason = SessionCache[session.SessionId].Auth.LastFailureReason = e switch {
+ MatrixException { ErrorCode: MatrixException.ErrorCodes.M_UNKNOWN_TOKEN } => UserAuth.FailureReason.InvalidToken,
+ HttpRequestException => UserAuth.FailureReason.NetworkError,
+ _ => UserAuth.FailureReason.UnknownError
+ };
+ await SaveStorage(log: true);
+
+ // await LoadStorage(true);
+ if (SessionCache[session.SessionId].Auth.LastFailureReason != reason) {
+ await Console.Error.WriteLineAsync(
+ $"Warning: Session {session.SessionId} failure reason changed during reload from {reason} to {SessionCache[session.SessionId].Auth.LastFailureReason}");
+ }
+
+ throw;
+ }
+ }).ToList();
+
+ while (tasks.Count != 0) {
+ var finished = await Task.WhenAny(tasks);
+ tasks.Remove(finished);
+ if (finished.IsFaulted) continue;
+
+ var result = await finished;
+ if (result != null) yield return result;
+ }
+ }
+
#endregion
#region Storage
@@ -170,7 +233,8 @@ public class RmuSessionStore(
CurrentSession = currentSession;
}
- private async Task SaveStorage() {
+ private async Task SaveStorage(bool log = false) {
+ if (log) logger.LogWarning("Saving {count} sessions to storage.", SessionCache.Count);
await storageService.DataStorageProvider!.SaveObjectAsync("rmu.sessions",
SessionCache.ToDictionary(
x => x.Key,
@@ -178,6 +242,7 @@ public class RmuSessionStore(
)
);
await storageService.DataStorageProvider.SaveObjectAsync("rmu.session", CurrentSession?.SessionId);
+ if (log) logger.LogWarning("{count} sessions saved to storage.", SessionCache.Count);
}
#endregion
@@ -190,29 +255,42 @@ public class RmuSessionStore(
}
private async Task MigrateFromMru() {
- logger.LogInformation("Migrating from MRU token namespace!");
var dsp = storageService.DataStorageProvider!;
- if (await dsp.ObjectExistsAsync("token")) {
- var oldToken = await dsp.LoadObjectAsync<UserAuth>("token");
- if (oldToken != null) {
- await dsp.SaveObjectAsync("rmu.token", oldToken);
- await dsp.DeleteObjectAsync("token");
+ if (await dsp.ObjectExistsAsync("token") || await dsp.ObjectExistsAsync("tokens")) {
+ logger.LogInformation("Migrating from unnamespaced localstorage!");
+ if (await dsp.ObjectExistsAsync("token")) {
+ var oldToken = await dsp.LoadObjectAsync<UserAuth>("token");
+ if (oldToken != null) {
+ await dsp.SaveObjectAsync("mru.token", oldToken);
+ await dsp.DeleteObjectAsync("token");
+ }
}
- }
- if (await dsp.ObjectExistsAsync("tokens")) {
- var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("tokens");
- if (oldTokens != null) {
- await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
- await dsp.DeleteObjectAsync("tokens");
+ if (await dsp.ObjectExistsAsync("tokens")) {
+ var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("tokens");
+ if (oldTokens != null) {
+ await dsp.SaveObjectAsync("mru.tokens", oldTokens);
+ await dsp.DeleteObjectAsync("tokens");
+ }
}
}
- if (await dsp.ObjectExistsAsync("mru.tokens")) {
- var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("mru.tokens");
- if (oldTokens != null) {
- await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
- await dsp.DeleteObjectAsync("mru.tokens");
+ if (await dsp.ObjectExistsAsync("mru.token") || await dsp.ObjectExistsAsync("mru.tokens")) {
+ logger.LogInformation("Migrating from MRU token namespace!");
+ if (await dsp.ObjectExistsAsync("mru.token")) {
+ var oldToken = await dsp.LoadObjectAsync<UserAuth>("mru.token");
+ if (oldToken != null) {
+ await dsp.SaveObjectAsync("rmu.token", oldToken);
+ await dsp.DeleteObjectAsync("mru.token");
+ }
+ }
+
+ if (await dsp.ObjectExistsAsync("mru.tokens")) {
+ var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("mru.tokens");
+ if (oldTokens != null) {
+ await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
+ await dsp.DeleteObjectAsync("mru.tokens");
+ }
}
}
}
diff --git a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
index 215ad14..5c0fdab 100644
--- a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
+++ b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
@@ -12,7 +12,7 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate {
new() {
Name = "My new room",
RoomAliasName = "myroom",
- InitialState = new List<StateEvent> {
+ InitialState = new List<MatrixEvent> {
new() {
Type = "m.room.history_visibility",
TypedContent = new RoomHistoryVisibilityEventContent() {
diff --git a/MatrixUtils.Web/Classes/UserAuth.cs b/MatrixUtils.Web/Classes/UserAuth.cs
index 66476ae..16bb758 100644
--- a/MatrixUtils.Web/Classes/UserAuth.cs
+++ b/MatrixUtils.Web/Classes/UserAuth.cs
@@ -1,9 +1,11 @@
+using System.Text.Json.Serialization;
using LibMatrix.Responses;
namespace MatrixUtils.Web.Classes;
public class UserAuth : LoginResponse {
public UserAuth() { }
+
public UserAuth(LoginResponse login) {
Homeserver = login.Homeserver;
UserId = login.UserId;
@@ -12,4 +14,14 @@ public class UserAuth : LoginResponse {
}
public string? Proxy { get; set; }
-}
+
+ public FailureReason? LastFailureReason { get; set; }
+
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum FailureReason {
+ None,
+ InvalidToken,
+ NetworkError,
+ UnknownError
+ }
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj
index 18204d0..9100c87 100644
--- a/MatrixUtils.Web/MatrixUtils.Web.csproj
+++ b/MatrixUtils.Web/MatrixUtils.Web.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LinkIncremental>true</LinkIncremental>
@@ -10,40 +10,44 @@
<UseBlazorWebAssembly>true</UseBlazorWebAssembly>
<BlazorEnableCompression>false</BlazorEnableCompression>
+ <CompressionEnabled>false</CompressionEnabled>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<BlazorCacheBootResources>false</BlazorCacheBootResources>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
+ <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
+ <WasmEnableHotReload>false</WasmEnableHotReload>
</PropertyGroup>
<!-- Explicitly disable all the unused runtime things trimming would have removed anyways -->
<!-- https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options -->
-<!-- <PropertyGroup>-->
-<!-- <AutoreleasePoolSupport>false</AutoreleasePoolSupport> <!– Browser != MacOS –>-->
-<!-- <MetadataUpdaterSupport>false</MetadataUpdaterSupport> <!– Unreliable –>-->
-<!-- <DebuggerSupport>false</DebuggerSupport> <!– Unreliable –>-->
-<!-- <InvariantGlobalization>true</InvariantGlobalization> <!– invariant globalization is fine –>-->
-<!-- <!– unused features –>-->
-<!-- <EventSourceSupport>false</EventSourceSupport>-->
-<!-- <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>-->
-<!-- <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>-->
-<!-- <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>-->
-<!-- <MetricsSupport>false</MetricsSupport>-->
-<!-- <UseNativeHttpHandler>false</UseNativeHttpHandler>-->
-<!-- <XmlResolverIsNetworkingEnabledByDefault>false</XmlResolverIsNetworkingEnabledByDefault>-->
-<!-- <BuiltInComInteropSupport>false</BuiltInComInteropSupport>-->
-<!-- <CustomResourceTypesSupport>false</CustomResourceTypesSupport>-->
-<!-- <EnableCppCLIHostActivation>false</EnableCppCLIHostActivation>-->
-<!-- <StartupHookSupport>false</StartupHookSupport>-->
-<!-- </PropertyGroup>-->
+ <!-- <PropertyGroup>-->
+ <!-- <AutoreleasePoolSupport>false</AutoreleasePoolSupport> <!– Browser != MacOS –>-->
+ <!-- <MetadataUpdaterSupport>false</MetadataUpdaterSupport> <!– Unreliable –>-->
+ <!-- <DebuggerSupport>false</DebuggerSupport> <!– Unreliable –>-->
+ <!-- <InvariantGlobalization>true</InvariantGlobalization> <!– invariant globalization is fine –>-->
+ <!-- <!– unused features –>-->
+ <!-- <EventSourceSupport>false</EventSourceSupport>-->
+ <!-- <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>-->
+ <!-- <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>-->
+ <!-- <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>-->
+ <!-- <MetricsSupport>false</MetricsSupport>-->
+ <!-- <UseNativeHttpHandler>false</UseNativeHttpHandler>-->
+ <!-- <XmlResolverIsNetworkingEnabledByDefault>false</XmlResolverIsNetworkingEnabledByDefault>-->
+ <!-- <BuiltInComInteropSupport>false</BuiltInComInteropSupport>-->
+ <!-- <CustomResourceTypesSupport>false</CustomResourceTypesSupport>-->
+ <!-- <EnableCppCLIHostActivation>false</EnableCppCLIHostActivation>-->
+ <!-- <StartupHookSupport>false</StartupHookSupport>-->
+ <!-- </PropertyGroup>-->
<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0"/>
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0"/>
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.8" PrivateAssets="all" />
- <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.8" />
- <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.8" />
- <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.17.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0"/>
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0" PrivateAssets="all"/>
+ <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.0"/>
+ <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="10.0.0"/>
+ <PackageReference Include="SpawnDev.BlazorJS" Version="2.47.0"/>
+ <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.26.0"/>
</ItemGroup>
<ItemGroup>
@@ -52,8 +56,8 @@
</ItemGroup>
<ItemGroup>
-<!-- <PackageReference Include="ArcaneLibs.Blazor.Components" Version="1.0.0-preview.20241210-161342" Condition="'$(Configuration)' == 'Release'"/>-->
-<!-- <ProjectReference Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj" Condition="'$(Configuration)' == 'Debug'"/>-->
+ <!-- <PackageReference Include="ArcaneLibs.Blazor.Components" Version="1.0.0-preview.20241210-161342" Condition="'$(Configuration)' == 'Release'"/>-->
+ <!-- <ProjectReference Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj" Condition="'$(Configuration)' == 'Debug'"/>-->
<ProjectReference Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj"/>
</ItemGroup>
diff --git a/MatrixUtils.Web/Pages/Dev/DevOptions.razor b/MatrixUtils.Web/Pages/Dev/DevOptions.razor
index 33e577f..281cf07 100644
--- a/MatrixUtils.Web/Pages/Dev/DevOptions.razor
+++ b/MatrixUtils.Web/Pages/Dev/DevOptions.razor
@@ -21,7 +21,7 @@
</p>
<details>
<summary>Manage local sessions</summary>
-
+
</details>
@if (userSettings is not null) {
@@ -40,10 +40,14 @@
@code {
private RmuSessionStore.Settings? userSettings { get; set; }
+
protected override async Task OnInitializedAsync() {
- // userSettings = await TieredStorage.DataStorageProvider.LoadObjectAsync<RmuSessionStore.Settings>("rmu.settings");
-
- await base.OnInitializedAsync();
+ await (Task)typeof(RmuSessionStore).GetMethod("LoadStorage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ ?.Invoke(sessionStore, [true])!;
+ await foreach (var _ in sessionStore.TryGetAllHomeservers()) { }
+
+ await (Task)typeof(RmuSessionStore).GetMethod("SaveStorage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ ?.Invoke(sessionStore, [true])!;
}
private async Task LogStuff() {
@@ -58,8 +62,9 @@
foreach (var key in keys) {
data.Add(key, await TieredStorage.DataStorageProvider.LoadObjectAsync<object>(key));
}
+
var dataUri = "data:application/json;base64,";
- dataUri += Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data)));
+ dataUri += Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data)));
await JSRuntime.InvokeVoidAsync("window.open", dataUri, "_blank");
}
@@ -69,6 +74,7 @@
foreach (var (key, value) in data) {
await TieredStorage.DataStorageProvider.SaveObjectAsync(key, value);
}
+
NavigationManager.NavigateTo(NavigationManager.Uri, true, true);
}
diff --git a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
index c636c56..722f9b3 100644
--- a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
+++ b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
@@ -72,6 +72,7 @@
public bool HasClientWellKnown => WellKnownResolutionResult?.ClientWellKnown is { Content.Homeserver.BaseUrl: { Length: > 0 } };
public bool HasServerWellKnown => WellKnownResolutionResult?.ServerWellKnown is { Content.Homeserver.Length: > 0 };
public bool HasSupportWellKnown => WellKnownResolutionResult?.SupportWellKnown?.Content is not null and not { SupportPage: null, Contacts: null or { Count: 0 } };
+ public bool HasPolicyServerWellKnown => WellKnownResolutionResult?.PolicyServerWellKnown?.Content is not null and not { PublicKey: null or "" };
}
private async Task Execute() {
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
index e9d0cd2..5ccaca9 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
@@ -96,7 +96,7 @@
}
}
- private StateEventResponse? Event { get; set; }
+ private MatrixEventResponse? Event { get; set; }
private string? EventJson {
get;
@@ -140,7 +140,7 @@
private async Task ExpandEventJson() {
Console.WriteLine("Expanding event JSON...");
if (!string.IsNullOrWhiteSpace(EventJson)) {
- Event = JsonSerializer.Deserialize<StateEventResponse>(EventJson);
+ Event = JsonSerializer.Deserialize<MatrixEventResponse>(EventJson);
MxcUrl = Event?.ContentAs<RoomMessageEventContent>()?.Url;
Console.WriteLine($"MXC URL: {MxcUrl}");
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/RoomQuery/SynapseRoomQueryFilter.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/RoomQuery/SynapseRoomQueryFilter.razor
index eb168f4..f1c5907 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/RoomQuery/SynapseRoomQueryFilter.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/RoomQuery/SynapseRoomQueryFilter.razor
@@ -1,46 +1,70 @@
@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters
+@using MatrixUtils.Web.Shared.FilterComponents
<div style="margin-left: 8px; margin-bottom: 8px;">
<u style="display: block;">String contains</u>
- <span class="tile tile280">Room ID: <FancyTextBox @bind-Value="@Filter.RoomIdContains"></FancyTextBox></span>
- <span class="tile tile280">Room name: <FancyTextBox @bind-Value="@Filter.NameContains"></FancyTextBox></span>
- <span class="tile tile280">Canonical alias: <FancyTextBox @bind-Value="@Filter.CanonicalAliasContains"></FancyTextBox></span>
- <span class="tile tile280">Creator: <FancyTextBox @bind-Value="@Filter.CreatorContains"></FancyTextBox></span>
- <span class="tile tile280">Room version: <FancyTextBox @bind-Value="@Filter.VersionContains"></FancyTextBox></span>
- <span class="tile tile280">Encryption algorithm: <FancyTextBox @bind-Value="@Filter.EncryptionContains"></FancyTextBox></span>
- <span class="tile tile280">Join rules: <FancyTextBox @bind-Value="@Filter.JoinRulesContains"></FancyTextBox></span>
- <span class="tile tile280">Guest access: <FancyTextBox @bind-Value="@Filter.GuestAccessContains"></FancyTextBox></span>
- <span class="tile tile280">History visibility: <FancyTextBox @bind-Value="@Filter.HistoryVisibilityContains"></FancyTextBox></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.RoomId" Label="Room ID"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.Name" Label="Room name"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.CanonicalAlias" Label="Canonical alias"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.Creator" Label="Creator"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.Version" Label="Room version"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.Encryption" Label="Encryption algorithm"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.JoinRules" Label="Join rules"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.GuestAccess" Label="Guest access"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.HistoryVisibility" Label="History visibility"/></span>
+ <span class="tile tile280"><StringFilterComponent Filter="@Filter.Topic" Label="Topic"/></span>
<u style="display: block;">Optional checks</u>
- <span class="tile tile150">
- <InputCheckbox @bind-Value="@Filter.CheckFederation"></InputCheckbox> Is federated:
- @if (Filter.CheckFederation) {
- <InputCheckbox @bind-Value="@Filter.Federatable"></InputCheckbox>
- }
- </span>
- <span class="tile tile150">
- <InputCheckbox @bind-Value="@Filter.CheckPublic"></InputCheckbox> Is public:
- @if (Filter.CheckPublic) {
- <InputCheckbox @bind-Value="@Filter.Public"></InputCheckbox>
- }
- </span>
+ <span class="tile tile150"><BooleanFilterComponent Filter="@Filter.Federation" Label="Is federated"/></span>
+ <span class="tile tile150"><BooleanFilterComponent Filter="@Filter.Public" Label="Is public"/></span>
+ <span class="tile tile150"><BooleanFilterComponent Filter="@Filter.Tombstone" Label="Is tombstoned"/></span>
<u style="display: block;">Ranges</u>
<span class="tile center-children">
- <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsGreaterThan"/>
- <span class="range-sep">state events</span>
- <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsLessThan"/>
+ <InputCheckbox @bind-Value="@Filter.StateEvents.Enabled"/>
+ @if (!Filter.StateEvents.Enabled) {
+ <span>State events</span>
+ }
+ else {
+ <InputCheckbox @bind-Value="@Filter.StateEvents.CheckGreaterThan"/>
+ <span> </span>
+ <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEvents.GreaterThan"/>
+ <span class="range-sep">state events</span>
+ <InputCheckbox @bind-Value="@Filter.StateEvents.CheckLessThan"/>
+ <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEvents.LessThan"/>
+ }
</span>
<span class="tile center-children">
- <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersGreaterThan"></InputNumber><span class="range-sep">members</span><InputNumber
- max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersLessThan"></InputNumber>
+ <InputCheckbox @bind-Value="@Filter.JoinedMembers.Enabled"/>
+ @if (!Filter.JoinedMembers.Enabled) {
+ <span>Joined members</span>
+ }
+ else {
+ <InputCheckbox @bind-Value="@Filter.JoinedMembers.CheckGreaterThan"/>
+ <span> </span>
+ <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembers.GreaterThan"/>
+ <span class="range-sep">members</span>
+ <InputCheckbox @bind-Value="@Filter.JoinedMembers.CheckLessThan"/>
+ <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembers.LessThan"/>
+ }
</span>
<span class="tile center-children">
- <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersGreaterThan"></InputNumber><span
- class="range-sep">local members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int"
- @bind-Value="@Filter.JoinedLocalMembersLessThan"></InputNumber>
+ <InputCheckbox @bind-Value="@Filter.JoinedLocalMembers.Enabled"/>
+ <span> </span>
+ @if (!Filter.JoinedLocalMembers.Enabled) {
+ <span>Joined local members</span>
+ }
+ else {
+ <InputCheckbox @bind-Value="@Filter.JoinedLocalMembers.CheckGreaterThan"/>
+ <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembers.GreaterThan"/>
+ <span class="range-sep">local members</span>
+ <InputCheckbox @bind-Value="@Filter.JoinedLocalMembers.CheckLessThan"/>
+ <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembers.LessThan"/>
+ }
</span>
</div>
+@* @{ *@
+@* Console.WriteLine($"Rendered SynapseRoomQueryFilter with filter: {Filter.ToJson()}"); *@
+@* } *@
@code {
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
index 999e331..b0e6a89 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
@@ -1,8 +1,12 @@
+@using System.Text.Json.Serialization
+@using ArcaneLibs.Extensions
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using LibMatrix.Homeservers.Extensions.NamedCaches
@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests
@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses
-@if (string.IsNullOrWhiteSpace(Context.DeleteId)) {
+@if (string.IsNullOrWhiteSpace(Context.DeleteId) || EditorOnly) {
<span>Block room: </span>
<InputCheckbox @bind-Value="@Context.DeleteRequest.Block"/>
<br/>
@@ -34,6 +38,12 @@
<br/>
<span>Delete <b>ALL</b> local user media: </span>
<InputCheckbox @bind-Value="@Context.ExtraOptions.DeleteLocalUserMedia"></InputCheckbox>
+ <br/>
+ <span>Follow tombstone (if any): </span>
+ <InputCheckbox @bind-Value="@Context.ExtraOptions.FollowTombstone"/>
+ @if (!EditorOnly) {
+ <LinkButton InlineText="true" OnClickAsync="@FollowTombstoneAsync">Exec</LinkButton>
+ }
</details>
<details>
@@ -50,7 +60,19 @@
<br/>
</details>
- <LinkButton OnClickAsync="@DeleteRoom">Execute</LinkButton>
+ @if (!EditorOnly) {
+ <LinkButton OnClickAsync="@DeleteRoom">Execute</LinkButton>
+ }
+}
+else {
+ <pre>
+ @(_status?.ToJson() ?? "Loading status...")
+ </pre>
+ <br/>
+ <LinkButton InlineText="true" OnClickAsync="@OnComplete">[Stop tracking]</LinkButton>
+ if (_status?.Status == SynapseAdminRoomDeleteStatus.Failed) {
+ <LinkButton InlineText="true" OnClickAsync="@ForceDelete">[Force delete]</LinkButton>
+ }
}
@code {
@@ -61,14 +83,52 @@
[Parameter]
public required AuthenticatedHomeserverSynapse Homeserver { get; set; }
+ [Parameter]
+ public bool EditorOnly { get; set; }
+
private NamedCache<RoomShutdownContext> TaskMap { get; set; } = null!;
+ private SynapseAdminRoomDeleteStatus? _status = null;
+ private bool _isTracking = false;
protected override async Task OnInitializedAsync() {
+ if (EditorOnly) return;
TaskMap = new NamedCache<RoomShutdownContext>(Homeserver, "gay.rory.matrixutils.synapse_room_shutdown_tasks");
+ var existing = await TaskMap.GetValueAsync(Context.RoomId);
+ if (existing is not null) {
+ Context = existing;
+ }
+
+ if (Context.ExecuteImmediately)
+ await DeleteRoom();
+ }
+
+ protected override async Task OnAfterRenderAsync(bool firstRender) {
+ if (EditorOnly) return;
+ if (!_isTracking) {
+ if (!string.IsNullOrWhiteSpace(Context.DeleteId)) {
+ _isTracking = true;
+ _ = Task.Run(async () => {
+ do {
+ _status = await Homeserver.Admin.GetRoomDeleteStatus(Context.DeleteId);
+ StateHasChanged();
+ if (_status.Status == SynapseAdminRoomDeleteStatus.Complete) {
+ await OnComplete();
+ break;
+ }
+
+ await Task.Delay(1000);
+ } while (_status.Status != SynapseAdminRoomDeleteStatus.Failed && _status.Status != SynapseAdminRoomDeleteStatus.Complete);
+ });
+ }
+ }
}
public class RoomShutdownContext {
public required string RoomId { get; set; }
+
+ [JsonIgnore] // do NOT persist - this triggers immediate purging
+ public bool ExecuteImmediately { get; set; }
+
public string? DeleteId { get; set; }
public ExtraDeleteOptions ExtraOptions { get; set; } = new();
@@ -81,10 +141,11 @@
public SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom? RoomDetails { get; set; }
public class ExtraDeleteOptions {
- // room options
+ public bool FollowTombstone { get; set; }
+
+ // media options
public bool QuarantineLocalMedia { get; set; }
public bool QuarantineRemoteMedia { get; set; }
-
public bool DeleteRemoteMedia { get; set; }
// user options
@@ -95,9 +156,14 @@
}
public async Task OnComplete() {
+ if (EditorOnly) return;
+ Console.WriteLine($"Room shutdown task for {Context.RoomId} completed, removing from map.");
await OnCompleteLock.WaitAsync();
try {
- await TaskMap.RemoveValueAsync(Context.DeleteId!);
+ await TaskMap.RemoveValueAsync(Context.RoomId!);
+ }
+ catch (Exception e) {
+ Console.WriteLine("Failed to remove completed room shutdown task from map: " + e);
}
finally {
OnCompleteLock.Release();
@@ -105,9 +171,96 @@
}
public async Task DeleteRoom() {
+ if (EditorOnly) return;
+ if (Context.ExtraOptions.FollowTombstone) await FollowTombstoneAsync();
+
+ Console.WriteLine($"Deleting room {Context.RoomId} with options: " + Context.DeleteRequest.ToJson());
+
+ var resp = await Homeserver.Admin.DeleteRoom(Context.RoomId, Context.DeleteRequest, false);
+ Context.DeleteId = resp.DeleteId;
await TaskMap.SetValueAsync(Context.RoomId, Context);
}
private static readonly SemaphoreSlim OnCompleteLock = new(1, 1);
+ private async Task FollowTombstoneAsync() {
+ if (EditorOnly) return;
+ var tomb = await TryGetTombstoneAsync();
+ var content = tomb?.ContentAs<RoomTombstoneEventContent>();
+ if (content != null && !string.IsNullOrWhiteSpace(content.ReplacementRoom)) {
+ Console.WriteLine("Tombstone: " + tomb.ToJson());
+ if (!content.ReplacementRoom.StartsWith('!')) {
+ Console.WriteLine($"Invalid replacement room ID in tombstone: {content.ReplacementRoom}, ignoring!");
+ }
+ else {
+ var oldMembers = await Homeserver.Admin.GetRoomMembersAsync(Context.RoomId, localOnly: true);
+ var isKnownRoom = await Homeserver.Admin.CheckRoomKnownAsync(content.ReplacementRoom);
+ var targetMembers = isKnownRoom
+ ? await Homeserver.Admin.GetRoomMembersAsync(Context.RoomId, localOnly: true)
+ : new() { Members = [] };
+
+ var members = oldMembers.Members.Except(targetMembers.Members).ToList();
+ Console.WriteLine("To migrate: " + members.ToJson());
+ foreach (var member in members) {
+ var success = false;
+ do {
+ var sess = member == Homeserver.WhoAmI.UserId ? Homeserver : await Homeserver.Admin.GetHomeserverForUserAsync(member, TimeSpan.FromSeconds(15));
+ var oldRoom = sess.GetRoom(Context.RoomId);
+ var room = sess.GetRoom(content.ReplacementRoom);
+ try {
+ var servers = (await oldRoom.GetMembersByHomeserverAsync(joinedOnly: true))
+ .Select(x => new KeyValuePair<string, int>(x.Key, x.Value.Count))
+ .OrderByDescending(x => x.Key == "matrix.org" ? 0 : x.Value); // try anything else first, to reduce load on matrix.org
+
+ await room.JoinAsync(servers.Take(10).Select(x => x.Key).ToArray(), reason: "Automatically following tombstone as old room is being purged.", checkIfAlreadyMember: isKnownRoom);
+ Console.WriteLine($"Migrated {member} from {Context.RoomId} to {content.ReplacementRoom}");
+ success = true;
+ }
+ catch (Exception e) {
+ if (e is MatrixException { ErrorCode: "M_FORBIDDEN" }) {
+ Console.WriteLine($"Cannot migrate {member} to {content.ReplacementRoom}: {(e as MatrixException)!.GetAsJson()}");
+ success = true; // give up
+ continue;
+ }
+
+ Console.WriteLine($"Failed to invite {member} to {content.ReplacementRoom}: {e}");
+ success = false;
+ await Task.Delay(1000);
+ }
+ } while (!success);
+ }
+ }
+ }
+ }
+
+ private async Task<MatrixEventResponse?> TryGetTombstoneAsync() {
+ if (EditorOnly) return null;
+ try {
+ return (await Homeserver.Admin.GetRoomStateAsync(Context.RoomId, RoomTombstoneEventContent.EventId)).Events.FirstOrDefault(x => x.StateKey == "");
+ }
+ catch {
+ return null;
+ }
+ }
+
+ private async Task ForceDelete() {
+ if (EditorOnly) return;
+ Console.WriteLine($"Forcing purge for {Context.RoomId}!");
+ await OnCompleteLock.WaitAsync();
+ try {
+ var resp = await Homeserver.Admin.DeleteRoom(Context.RoomId, new() {
+ ForcePurge = true
+ }, waitForCompletion: false);
+ Context.DeleteId = resp.DeleteId;
+ await TaskMap.SetValueAsync(Context.RoomId, Context);
+ StateHasChanged();
+ }
+ catch (Exception e) {
+ Console.WriteLine("Failed to remove completed room shutdown task from map: " + e);
+ }
+ finally {
+ OnCompleteLock.Release();
+ }
+ }
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
index 5e45e5b..05899c8 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
@@ -1,5 +1,7 @@
@page "/HSAdmin/Synapse/RoomQuery"
@using System.Diagnostics.CodeAnalysis
+@using System.Text.Json
+@using ArcaneLibs.Blazor.Components.Services
@using Microsoft.AspNetCore.WebUtilities
@using ArcaneLibs.Extensions
@using LibMatrix
@@ -10,6 +12,7 @@
@using MatrixUtils.Web.Pages.HSAdmin.Synapse.Components
@using MatrixUtils.Web.Pages.HSAdmin.Synapse.Components.RoomQuery
@inject ILogger<RoomQuery> Logger
+@inject BlazorSaveFileService BlazorSaveFileService
<h3>Homeserver Administration - Room Query</h3>
@@ -21,40 +24,56 @@
<option value="@item.Key">@item.Value</option>
}
</select><br/>
-<label>Ascending: </label>
-<InputCheckbox @bind-Value="Ascending"/><br/>
+<InputCheckbox @bind-Value="Ascending"/>
+<label> Ascending</label><br/>
+<InputCheckbox @bind-Value="FetchV12PlusCreatorServer"/>
+<label> Fetch v12+ room creation homeserver</label>
+<LinkButton InlineText="true" OnClickAsync="FetchV12PlusCreatorServersAsync"> (Execute manually)</LinkButton><br/>
+<InputCheckbox @bind-Value="FetchTombstones"/>
+<label> Check for tombstone events</label>
+<LinkButton InlineText="true" OnClickAsync="FetchTombstoneEventsAsync"> (Execute manually)</LinkButton><br/>
+<InputCheckbox @bind-Value="SummarizeLocalMembers"/>
+<label> Fetch local member list for small rooms</label>
+<LinkButton InlineText="true" OnClickAsync="FetchLocalMemberEventsAsync"> (Execute manually)</LinkButton><br/>
+<InputCheckbox @bind-Value="ShowFullResultData"/>
+<label> Show full result data (JSON)</label><br/>
+<InputCheckbox @bind-Value="EnableMultiPurge"/>
+<label> Enable multi-purge mode</label>
+@if (EnableMultiPurge) {
+ <span> </span>
+ <LinkButton InlineText="true" OnClick="@MultiPurgeInvertSelection">[Invert selection]</LinkButton>
+ <span> </span>
+ <details style="display: inline-block;">
+ <summary>Edit purge options</summary>
+ <SynapseRoomShutdownWindowContent Context="@DefaultShutdownContext" Homeserver="Homeserver" EditorOnly="true"/>
+ </details>
+}
+else {
+ <br/>
+}
<details>
- <summary>
- <span>Local filtering (slow)</span>
- </summary>
+ <summary>Local filtering (slow)</summary>
<SynapseRoomQueryFilter Filter="@Filter"/>
</details>
-<button class="btn btn-primary" @onclick="Search">Search</button>
+<LinkButton OnClickAsync="@Search">Search</LinkButton>
+
+@if (EnableMultiPurge) {
+ <LinkButton Color="#FF8800" OnClick="@PurgeSelection">Purge selected rooms</LinkButton>
+}
<br/>
@if (Results.Count > 0) {
<p>Found @Results.Count rooms</p>
- @* <details> *@
- @* <summary>TSV data (copy/paste)</summary> *@
- @* <pre style="font-size: 0.6em;"> *@
- @* <table> *@
- @* @foreach (var res in Results) { *@
- @* <tr> *@
- @* <td style="padding: 8px;">@res.RoomId@("\t")</td> *@
- @* <td style="padding: 8px;">@res.CanonicalAlias@("\t")</td> *@
- @* <td style="padding: 8px;">@res.Creator@("\t")</td> *@
- @* <td style="padding: 8px;">@res.Name</td> *@
- @* </tr> *@
- @* } *@
- @* </table> *@
- @* </pre> *@
- @* </details> *@
}
@foreach (var room in Results) {
<div class="room-list-item">
@* <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem> *@
<p>
+ @if (EnableMultiPurge) {
+ <InputCheckbox @bind-Value="@room.MultiPurgeSelected"/>
+ <span> </span>
+ }
@if (!string.IsNullOrWhiteSpace(room.CanonicalAlias)) {
<span>@room.CanonicalAlias - </span>
}
@@ -71,12 +90,14 @@
</p>
<p>
<LinkButton OnClickAsync="@(() => DeleteRoom(room))">Delete room</LinkButton>
- <LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={room.RoomId}&via={room.RoomId.Split(':', 2)[1]}")">Resync state</LinkButton>
+ <LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={room.RoomId}&via={room.OriginHomeserver}")">Resync state</LinkButton>
+ <LinkButton OnClickAsync="@(() => ExportState(room))">@(room.JoinedLocalMembers == 0 ? "Try to export state" : "Export state")</LinkButton>
+ <LinkButton OnClickAsync="@(() => ForceJoin(room))">Force Join</LinkButton>
</p>
@{
List<string?> flags = [];
- if (true || room.JoinedLocalMembers > 0) {
+ if (room.JoinedLocalMembers > 0) {
flags.Add(room.JoinRules switch {
"public" => "Public",
"invite" => "Invite only",
@@ -88,7 +109,7 @@
"" => null,
_ => "unknown join rule: " + room.JoinRules
});
-
+
if (!string.IsNullOrWhiteSpace(room.Encryption)) flags.Add("encrypted");
if (!room.Federatable) flags.Add("unfederated");
@@ -124,7 +145,8 @@
<span>@room.StateEvents state events, room version @(room.Version ?? "1")</span><br/>
@if (room.TombstoneEvent is not null) {
var tombstoneContent = room.TombstoneEvent.ContentAs<RoomTombstoneEventContent>()!;
- <span>Room is tombstoned! Target room: @tombstoneContent.ReplacementRoom, message: @tombstoneContent.Body</span><br/>
+ <span>Room is tombstoned! Target room: @tombstoneContent.ReplacementRoom, message: @tombstoneContent.Body</span>
+ <br/>
}
@{
@@ -133,11 +155,23 @@
memberSummary += $": {string.Join(", ", room.LocalMembers)}";
}
}
- <span>@memberSummary</span>
- <details>
- <summary>Full result data</summary>
- <pre>@room.ToJson(ignoreNull: true)</pre>
- </details>
+ <span>@memberSummary</span><br/>
+ @if (!string.IsNullOrWhiteSpace(room.TopicEvent?.ContentAs<RoomTopicEventContent>()?.Topic)) {
+ <details>
+ <summary>Room topic</summary>
+ <pre>@(room.TopicEvent?.ContentAs<RoomTopicEventContent>()?.Topic)</pre>
+ </details>
+ }
+ @foreach (var ex in room.Exceptions) {
+ <span style="color: red;">@ex</span>
+ <br/>
+ }
+ @if (ShowFullResultData) {
+ <details>
+ <summary>Full result data</summary>
+ <pre>@room.ToJson(ignoreNull: true)</pre>
+ </details>
+ }
</div>
}
@* *@
@@ -162,10 +196,6 @@
</ModalWindow>
}
-<style>
-
-</style>
-
@code {
[Parameter]
@@ -180,6 +210,18 @@
[SupplyParameterFromQuery(Name = "ascending")]
public bool Ascending { get; set; } = true;
+ [Parameter]
+ [SupplyParameterFromQuery(Name = "FetchV12PlusCreatorServer")]
+ public bool FetchV12PlusCreatorServer { get; set; } = true;
+
+ [Parameter]
+ [SupplyParameterFromQuery(Name = "SummarizeLocalMembers")]
+ public bool SummarizeLocalMembers { get; set; } = true;
+
+ [Parameter]
+ [SupplyParameterFromQuery(Name = "FetchTombstones")]
+ public bool FetchTombstones { get; set; } = true;
+
private List<RoomInfo> Results { get; set; } = new();
private AuthenticatedHomeserverSynapse Homeserver { get; set; } = null!;
@@ -192,6 +234,21 @@
private NamedCache<SynapseRoomShutdownWindowContent.RoomShutdownContext> TaskMap { get; set; } = null!;
+ private SynapseRoomShutdownWindowContent.RoomShutdownContext DefaultShutdownContext { get; set; } = new() {
+ RoomId = "",
+ DeleteRequest = new() { Block = true, Purge = true, ForcePurge = false }
+ };
+
+ public bool ShowFullResultData {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ }
+
+ public bool EnableMultiPurge { get; set; }
+
protected override async Task OnInitializedAsync() {
var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is not AuthenticatedHomeserverSynapse synapse) {
@@ -213,67 +270,95 @@
foreach (var (key, value) in QueryHelpers.ParseQuery(new Uri(NavigationManager.Uri).Query)) {
switch (key) {
case "RoomIdContains":
- Filter.RoomIdContains = value[0]!;
+ Filter.RoomId.Enabled = Filter.RoomId.CheckValueContains = true;
+ Filter.RoomId.ValueContains = value[0]!;
break;
case "NameContains":
- Filter.NameContains = value[0]!;
+ Filter.Name.Enabled = Filter.Name.CheckValueContains = true;
+ Filter.Name.ValueContains = value[0]!;
break;
case "CanonicalAliasContains":
- Filter.CanonicalAliasContains = value[0]!;
+ Filter.CanonicalAlias.Enabled = Filter.CanonicalAlias.CheckValueContains = true;
+ Filter.CanonicalAlias.ValueContains = value[0]!;
break;
case "VersionContains":
- Filter.VersionContains = value[0]!;
+ Filter.Version.Enabled = Filter.Version.CheckValueContains = true;
+ Filter.Version.ValueContains = value[0]!;
break;
case "CreatorContains":
- Filter.CreatorContains = value[0]!;
+ Filter.Creator.Enabled = Filter.Creator.CheckValueContains = true;
+ Filter.Creator.ValueContains = value[0]!;
break;
case "EncryptionContains":
- Filter.EncryptionContains = value[0]!;
+ Filter.Encryption.Enabled = Filter.Encryption.CheckValueContains = true;
+ Filter.Encryption.ValueContains = value[0]!;
break;
case "JoinRulesContains":
- Filter.JoinRulesContains = value[0]!;
+ Filter.JoinRules.Enabled = Filter.JoinRules.CheckValueContains = true;
+ Filter.JoinRules.ValueContains = value[0]!;
break;
case "GuestAccessContains":
- Filter.GuestAccessContains = value[0]!;
+ Filter.GuestAccess.Enabled = Filter.GuestAccess.CheckValueContains = true;
+ Filter.GuestAccess.ValueContains = value[0]!;
break;
case "HistoryVisibilityContains":
- Filter.HistoryVisibilityContains = value[0]!;
+ Filter.HistoryVisibility.Enabled = Filter.HistoryVisibility.CheckValueContains = true;
+ Filter.HistoryVisibility.ValueContains = value[0]!;
break;
case "Federatable":
- Filter.Federatable = bool.Parse(value[0]!);
- Filter.CheckFederation = true;
+ Filter.Federation = new() {
+ Enabled = true,
+ Value = bool.Parse(value[0]!)
+ };
break;
case "Public":
- Filter.Public = value[0] == "true";
- Filter.CheckPublic = true;
+ Filter.Public = new() {
+ Enabled = true,
+ Value = bool.Parse(value[0]!)
+ };
break;
case "JoinedMembersGreaterThan":
- Filter.JoinedMembersGreaterThan = int.Parse(value[0]!);
+ Filter.JoinedMembers.Enabled = Filter.JoinedLocalMembers.CheckGreaterThan = true;
+ Filter.JoinedMembers.GreaterThan = int.Parse(value[0]!);
break;
case "JoinedMembersLessThan":
- Filter.JoinedMembersLessThan = int.Parse(value[0]!);
+ Filter.JoinedMembers.Enabled = Filter.JoinedLocalMembers.CheckLessThan = true;
+ Filter.JoinedMembers.LessThan = int.Parse(value[0]!);
break;
case "JoinedLocalMembersGreaterThan":
- Filter.JoinedLocalMembersGreaterThan = int.Parse(value[0]!);
+ Filter.JoinedLocalMembers.Enabled = Filter.JoinedLocalMembers.CheckGreaterThan = true;
+ Filter.JoinedLocalMembers.GreaterThan = int.Parse(value[0]!);
break;
case "JoinedLocalMembersLessThan":
- Filter.JoinedLocalMembersLessThan = int.Parse(value[0]!);
+ Filter.JoinedLocalMembers.Enabled = Filter.JoinedLocalMembers.CheckLessThan = true;
+ Filter.JoinedLocalMembers.LessThan = int.Parse(value[0]!);
break;
case "StateEventsGreaterThan":
- Filter.StateEventsGreaterThan = int.Parse(value[0]!);
+ Filter.StateEvents.Enabled = Filter.StateEvents.CheckGreaterThan = true;
+ Filter.StateEvents.GreaterThan = int.Parse(value[0]!);
break;
case "StateEventsLessThan":
- Filter.StateEventsLessThan = int.Parse(value[0]!);
+ Filter.StateEvents.Enabled = Filter.StateEvents.CheckLessThan = true;
+ Filter.StateEvents.LessThan = int.Parse(value[0]!);
break;
case "Execute":
execute = true;
break;
+ case "order_by":
+ case "name_search":
+ case "ascending":
+ case "FetchV12PlusCreatorServer":
+ case "SummarizeLocalMembers":
+ case "FetchTombstones":
+ break;
default:
Console.WriteLine($"Unknown query parameter: {key}");
break;
}
}
+ StateHasChanged();
+
if (execute)
_ = Search();
@@ -282,7 +367,27 @@
private async Task Search() {
Results.Clear();
- var searchRooms = Homeserver.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator();
+ Console.WriteLine("Starting search... Parameters: " + new {
+ orderBy = OrderBy!,
+ dir = Ascending ? "f" : "b",
+ searchTerm = SearchTerm,
+ localFilter = Filter,
+ chunkLimit = 1000,
+ fetchTombstones = FetchTombstones,
+ fetchTopics = true,
+ fetchCreateEvents = true
+ }.ToJson());
+ var searchRooms = Homeserver.Admin.SearchRoomsAsync(
+ orderBy: OrderBy!,
+ dir: Ascending ? "f" : "b",
+ searchTerm: SearchTerm,
+ localFilter: Filter,
+ chunkLimit: 1000,
+ fetchTombstones: FetchTombstones,
+ fetchTopics: true,
+ fetchCreateEvents: true
+ ).GetAsyncEnumerator();
+ var joinedRooms = await Homeserver.GetJoinedRooms();
while (await searchRooms.MoveNextAsync()) {
var room = searchRooms.Current;
@@ -300,12 +405,27 @@
HistoryVisibility = room.HistoryVisibility,
StateEvents = room.StateEvents,
JoinedMembers = room.JoinedMembers,
- JoinedLocalMembers = room.JoinedLocalMembers
+ JoinedLocalMembers = room.JoinedLocalMembers,
+ OriginHomeserver =
+ Homeserver.GetRoom(room.RoomId).IsV12PlusRoomId
+ ? room.RoomId.Split(':', 2).Skip(1).FirstOrDefault(string.Empty)
+ : string.Empty
};
+ if (string.IsNullOrWhiteSpace(roomInfo.OriginHomeserver) && FetchV12PlusCreatorServer) {
+ try {
+ if (joinedRooms.Any(x => x.RoomId == room.RoomId))
+ roomInfo.OriginHomeserver = await Homeserver.GetRoom(room.RoomId).GetOriginHomeserverAsync();
+ else roomInfo.OriginHomeserver = (await Homeserver.Admin.GetRoomStateAsync(room.RoomId, RoomCreateEventContent.EventId)).Events.FirstOrDefault()?.Sender?.Split(':', 2)[1];
+ }
+ catch (MatrixException e) {
+ roomInfo.Exceptions.Add($"While getting origin homeserver: {e.GetAsObject().ToJson(indent: false, ignoreNull: true)}");
+ }
+ }
+
Results.Add(roomInfo);
- if ((Results.Count <= 200 && Results.Count % 10 == 0) || Results.Count % 1000 == 0) {
+ if ((Results.Count <= 200 && Results.Count % 10 == 0 && FetchV12PlusCreatorServer) || Results.Count % 1000 == 0) {
StateHasChanged();
await Task.Yield();
await Task.Delay(1);
@@ -314,97 +434,29 @@
StateHasChanged();
- var getLocalMembersTasks = Results
- .Where(x => x.JoinedLocalMembers is > 0 and < 100)
- .Select(async r => {
- var members = (await Homeserver.Admin.GetRoomMembersAsync(r.RoomId)).Members.Where(x => x.EndsWith(":" + Homeserver.ServerName)).ToList();
- r.LocalMembers = members;
- }
- );
- await Task.WhenAll(getLocalMembersTasks);
-
- var getTombstoneTasks = Results
- .Select(async r => {
- var state = await Homeserver.Admin.GetRoomStateAsync(r.RoomId, type: "m.room.tombstone");
- var tombstone = state.Events.FirstOrDefault(x => x is { StateKey: "", Type: "m.room.tombstone" });
- if (tombstone is { } tombstoneEvent) {
- r.TombstoneEvent = tombstoneEvent;
- }
- });
- await Task.WhenAll(getTombstoneTasks);
+ if (FetchV12PlusCreatorServer) await FetchV12PlusCreatorServersAsync(false);
+ if (SummarizeLocalMembers) await FetchLocalMemberEventsAsync(false);
+ // if (CheckTombstone) await FetchTombstoneEventsAsync(false);
StateHasChanged();
}
- Task DeleteRoom(RoomInfo room) {
- DeleteRequests.TryAdd(room.RoomId, new() { RoomId = room.RoomId, RoomDetails = room, DeleteRequest = new() { Block = true, Purge = true, ForcePurge = false } });
+ private Task DeleteRoom(RoomInfo room, bool executeWithoutConfirmation = false) {
+ var dc = JsonSerializer.Deserialize<SynapseRoomShutdownWindowContent.RoomShutdownContext>(DefaultShutdownContext.ToJson())!;
+ dc.RoomId = room.RoomId;
+ dc.RoomDetails = room;
+ dc.ExecuteImmediately = executeWithoutConfirmation;
+ DeleteRequests.TryAdd(room.RoomId, dc);
StateHasChanged();
return Task.CompletedTask;
}
- //
- // private async Task DeleteRoom() {
- // if (DeleteRequest is { } deleteRequest) {
- // var media = await Homeserver.Admin.GetRoomMediaAsync(deleteRequest.RoomId);
- // if (deleteRequest.DeleteRequest.QuarantineRemoteMedia) {
- // foreach (var remoteMedia in media.Remote) {
- // await Homeserver.Admin.QuarantineMediaById(remoteMedia);
- // }
- // }
- //
- // if (deleteRequest.DeleteRequest.DeleteRemoteMedia) {
- // foreach (var remoteMedia in media.Remote) {
- // await Homeserver.Admin.DeleteMediaById(remoteMedia);
- // }
- // }
- // else if (deleteRequest.DeleteRequest.QuarantineLocalMedia) {
- // foreach (var localMedia in media.Local) {
- // await Homeserver.Admin.QuarantineMediaById(localMedia);
- // }
- // }
- //
- // var deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false);
- // DeleteRequest = null;
- // List<string> alreadyCleanedUsers = [];
- // while (true) {
- // var status = await Homeserver.Admin.GetRoomDeleteStatus(deleteId.DeleteId);
- // DeleteStatuses[deleteRequest.RoomId] = status;
- // StateHasChanged();
- // await Task.Delay(5000);
- // if (status.Status == "complete") {
- // DeleteStatuses.Remove(deleteRequest.RoomId);
- // StateHasChanged();
- // break;
- // }
- //
- // if (status.Status == "failed") {
- // deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false);
- // }
- //
- // var newCleanedUsers = status.ShutdownRoom?.KickedUsers?.Except(alreadyCleanedUsers).ToList();
- // if (newCleanedUsers is not null) {
- // alreadyCleanedUsers.AddRange(newCleanedUsers);
- // foreach (var user in newCleanedUsers) {
- // if (deleteRequest.DeleteRequest.SuspendLocalUsers) {
- // // await Homeserver.Admin.(user);
- // }
- //
- // if (deleteRequest.DeleteRequest.QuarantineLocalUserMedia) {
- // await Homeserver.Admin.QuarantineMediaByUserId(user);
- // }
- //
- // if (deleteRequest.DeleteRequest.DeleteLocalUserMedia) {
- // var userMedia = Homeserver.Admin.GetUserMediaEnumerableAsync(user);
- // await foreach (var mediaEntry in userMedia) {
- // await Homeserver.Admin.DeleteMediaById(mediaEntry.MediaId);
- // }
- // }
- // }
- // }
- // }
- // }
- // }
+ private void PurgeSelection() {
+ foreach (var room in Results.Where(x => x.MultiPurgeSelected)) {
+ DeleteRoom(room, true);
+ }
+ }
private readonly Dictionary<string, string> validOrderBy = new() {
{ "name", "Room name" },
@@ -424,10 +476,143 @@
private class RoomInfo : SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom {
public List<string>? LocalMembers { get; set; }
- public StateEventResponse? TombstoneEvent { get; set; }
+ public required string OriginHomeserver { get; set; }
[field: AllowNull, MaybeNull]
public string MemberSummary => field ??= $"{JoinedMembers} members, of which {JoinedLocalMembers} are on this server";
+
+ public List<string> Exceptions { get; set; } = [];
+ public bool MultiPurgeSelected { get; set; }
+ }
+
+ private async Task ExportState(RoomInfo room) {
+ try {
+ var state = await Homeserver.Admin.GetRoomStateAsync(room.RoomId);
+ var json = state.ToJson();
+ await BlazorSaveFileService.SaveFileAsync($"{room.RoomId.Replace(":", "_")}_state.json", System.Text.Encoding.UTF8.GetBytes(json), "application/json");
+ }
+ catch (Exception e) {
+ Logger.LogError(e, "Failed to export room state for {RoomId}", room.RoomId);
+ }
+ }
+
+ private async Task ForceJoin(RoomInfo room) {
+ try {
+ await Homeserver.GetRoom(room.RoomId).JoinAsync([Homeserver.ServerName]);
+ }
+ catch (Exception e) {
+ Logger.LogError(e, "Failed to force-join room {RoomId}", room.RoomId);
+ // await Homeserver.Admin.room
+ }
+ }
+
+ private SemaphoreSlim _concurrencyLimiter = new SemaphoreSlim(16, 16);
+
+ private async Task FetchV12PlusCreatorServersAsync() => await FetchV12PlusCreatorServersAsync(true);
+
+ private async Task FetchV12PlusCreatorServersAsync(bool rerender) {
+ var joinedRooms = await Homeserver.GetJoinedRooms();
+ var tasks = Results
+ .Where(x => string.IsNullOrWhiteSpace(x.OriginHomeserver))
+ .Select(async r => {
+ if (!string.IsNullOrWhiteSpace(r.Creator) && r.Creator.Contains(':')) {
+ r.OriginHomeserver = r.Creator.Split(':', 2)[1];
+ return;
+ }
+
+ if (r.CreateEvent != null && !string.IsNullOrWhiteSpace(r.CreateEvent.Sender) && r.CreateEvent.Sender.Contains(':')) {
+ r.OriginHomeserver = r.CreateEvent.Sender.Split(':', 2)[1];
+ return;
+ }
+
+ await _concurrencyLimiter.WaitAsync();
+ try {
+ if (joinedRooms.Any(x => x.RoomId == r.RoomId))
+ r.OriginHomeserver = await Homeserver.GetRoom(r.RoomId).GetOriginHomeserverAsync();
+ else r.OriginHomeserver = (await Homeserver.Admin.GetRoomStateAsync(r.RoomId, RoomCreateEventContent.EventId)).Events.FirstOrDefault()?.Sender?.Split(':', 2)[1];
+ }
+ catch (MatrixException e) {
+ r.Exceptions.Add($"While getting origin homeserver: {e.GetAsObject().ToJson(indent: false, ignoreNull: true)}");
+ }
+ catch (Exception e) {
+ Console.WriteLine($"Failed to get origin homeserver for {r.RoomId}, unhandled exception: " + e);
+ }
+ finally {
+ _concurrencyLimiter.Release();
+ }
+ });
+
+ await Task.WhenAll(tasks);
+
+ if (rerender)
+ StateHasChanged();
+ }
+
+ private async Task FetchTombstoneEventsAsync() => await FetchTombstoneEventsAsync(true);
+
+ private async Task FetchTombstoneEventsAsync(bool rerender) {
+ var getTombstoneTasks = Results
+ .Where(x => x.TombstoneEvent is null)
+ .Select(async r => {
+ await _concurrencyLimiter.WaitAsync();
+ try {
+ var state = await Homeserver.Admin.GetRoomStateAsync(r.RoomId, type: "m.room.tombstone");
+ var tombstone = state.Events.FirstOrDefault(x => x is { StateKey: "", Type: "m.room.tombstone" });
+ if (tombstone is { } tombstoneEvent) {
+ r.TombstoneEvent = tombstoneEvent;
+ }
+ }
+ catch (MatrixException e) {
+ r.Exceptions.Add($"While checking for tombstone: {e.GetAsObject().ToJson(indent: false, ignoreNull: true)}");
+ }
+ catch (Exception e) {
+ Console.WriteLine($"Failed to check tombstone for {r.RoomId}, unhandled exception: " + e);
+ }
+ finally {
+ _concurrencyLimiter.Release();
+ }
+ });
+
+ await Task.WhenAll(getTombstoneTasks);
+
+ if (rerender)
+ StateHasChanged();
+ }
+
+ private async Task FetchLocalMemberEventsAsync() => await FetchLocalMemberEventsAsync(true);
+
+ private async Task FetchLocalMemberEventsAsync(bool rerender) {
+ var getLocalMembersTasks = Results
+ .Where(x => x.LocalMembers is null && x.JoinedLocalMembers is > 0 and < 100)
+ .Select(async r => {
+ await _concurrencyLimiter.WaitAsync();
+ try {
+ var members = (await Homeserver.Admin.GetRoomMembersAsync(r.RoomId)).Members.Where(x => x.EndsWith(":" + Homeserver.ServerName)).ToList();
+ r.LocalMembers = members;
+ }
+ catch (MatrixException e) {
+ r.Exceptions.Add($"While fetching local members: {e.GetAsObject().ToJson(ignoreNull: true, indent: false)}");
+ }
+ catch (Exception e) {
+ Console.WriteLine($"Failed to fetch local members for {r.RoomId}, unhandled exception: " + e);
+ }
+ finally {
+ _concurrencyLimiter.Release();
+ }
+ });
+
+ await Task.WhenAll(getLocalMembersTasks);
+
+ if (rerender)
+ StateHasChanged();
+ }
+
+ private void MultiPurgeInvertSelection() {
+ foreach (var room in Results) {
+ room.MultiPurgeSelected ^= true;
+ }
+
+ StateHasChanged();
}
}
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
index c2446a2..3cc5a6a 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
@@ -39,7 +39,6 @@
}
@* stage 3 *@
-
@if (Stage == 3) {
<p>Rejoining room, please wait...</p>
<p>Members left to restore: </p>
@@ -82,7 +81,7 @@
private Exception? Error { get; set; }
// Stage 1
- private List<StateEventResponse>? Members { get; set; }
+ private List<MatrixEventResponse>? Members { get; set; }
// Stage 2
private SynapseAdminRoomDeleteStatus? DeleteStatus { get; set; }
diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs
index 16051b8..c58114e 100644
--- a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs
+++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs
@@ -13,9 +13,10 @@ public class ClientSyncWrapper(AuthenticatedHomeserverGeneric homeserver) : Noti
MinimumDelay = TimeSpan.FromMilliseconds(2000),
IsInitialSync = false
};
+
private string _status = "Loading...";
- public ObservableCollection<StateEvent> AccountData { get; set; } = new();
+ public ObservableCollection<MatrixEvent> AccountData { get; set; } = new();
public ObservableCollection<RoomInfo> Rooms { get; set; } = new();
public string Status {
@@ -29,13 +30,12 @@ public class ClientSyncWrapper(AuthenticatedHomeserverGeneric homeserver) : Noti
Status = $"[{DateTime.Now:s}] Syncing...";
await foreach (var response in resp) {
Task.Yield();
- Status = $"[{DateTime.Now:s}] {response.Rooms?.Join?.Count ?? 0 + response.Rooms?.Invite?.Count ?? 0 + response.Rooms?.Leave?.Count ?? 0} rooms, {response.AccountData?.Events?.Count ?? 0} account data, {response.ToDevice?.Events?.Count ?? 0} to-device, {response.DeviceLists?.Changed?.Count ?? 0} device lists, {response.Presence?.Events?.Count ?? 0} presence updates";
+ Status =
+ $"[{DateTime.Now:s}] {response.Rooms?.Join?.Count ?? 0 + response.Rooms?.Invite?.Count ?? 0 + response.Rooms?.Leave?.Count ?? 0} rooms, {response.AccountData?.Events?.Count ?? 0} account data, {response.ToDevice?.Events?.Count ?? 0} to-device, {response.DeviceLists?.Changed?.Count ?? 0} device lists, {response.Presence?.Events?.Count ?? 0} presence updates";
await HandleSyncResponse(response);
await Task.Yield();
}
}
- private async Task HandleSyncResponse(SyncResponse resp) {
-
- }
+ private async Task HandleSyncResponse(SyncResponse resp) { }
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
index 360548d..7199934 100644
--- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
@@ -26,7 +26,7 @@
<InputCheckbox @bind-Value="SetupData.DmSpaceInfo.LayerByUser"></InputCheckbox>
Create sub-spaces per user
</p>
-
+
<br/>
<LinkButton OnClickAsync="@Disband" Color="#FF0000">Disband</LinkButton>
<LinkButton OnClickAsync="@Execute">Next</LinkButton>
@@ -78,7 +78,7 @@ else {
userRooms.Add(room);
}
- var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable();
+ var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncResultEnumerable();
await foreach (var room in roomChecks)
if (room.HasValue)
spaces.TryAdd(room.Value.id, room.Value.roomInfo);
@@ -109,8 +109,8 @@ else {
public async Task<(string id, RoomInfo roomInfo)?> GetFeasibleSpaces(GenericRoom room) {
try {
var ri = new RoomInfo(room);
-
- await foreach(var evt in room.GetFullStateAsync())
+
+ await foreach (var evt in room.GetFullStateAsync())
ri.StateEvents.Add(evt);
var powerLevels = (await ri.GetStateEvent(RoomPowerLevelEventContent.EventId)).TypedContent as RoomPowerLevelEventContent;
@@ -118,7 +118,7 @@ else {
Console.WriteLine($"No permission to send m.space.child in {room.RoomId}...");
return null;
}
-
+
Status = $"Found viable space: {ri.RoomName}";
if (!string.IsNullOrWhiteSpace(SetupData.DmSpaceConfiguration!.DMSpaceId)) {
if (await room.GetStateOrNullAsync<DMSpaceInfo>(DMSpaceInfo.EventId) is { } dsi) {
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
index 25d1629..ed65e94 100644
--- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
@@ -38,7 +38,9 @@ else {
}
@foreach (var (room, usersList) in duplicateDmRooms) {
<ModalWindow Title="Duplicate room found" X="_offset += 30" Y="_offset">
- <p>Found room assigned to multiple users: <RoomListItem RoomInfo="@room"></RoomListItem></p>
+ <p>Found room assigned to multiple users:
+ <RoomListItem RoomInfo="@room"></RoomListItem>
+ </p>
<p>Users:</p>
@foreach (var userProfileResponse in usersList) {
<LinkButton OnClickAsync="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))">
@@ -141,12 +143,12 @@ else {
}
var roomList = new List<RoomInfo>();
- var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable();
+ var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncResultEnumerable();
await foreach (var result in tasks)
roomList.Add(result);
return (userProfile, roomList);
// StateHasChanged();
- }).ToAsyncEnumerable();
+ }).ToAsyncResultEnumerable();
await foreach (var res in results) {
SetupData.DMRooms.Add(res.userProfile, res.roomList);
// Status = $"Listed {dmRooms.Count} users";
@@ -181,8 +183,8 @@ else {
await roomInfo.FetchAllStateAsync();
roomMembers[roomInfo] = new();
// roomInfo.CreationEventContent = await room.GetCreateEventAsync();
-
- if(roomInfo.RoomName == room.RoomId)
+
+ if (roomInfo.RoomName == room.RoomId)
try {
roomInfo.RoomName = await room.GetNameOrFallbackAsync();
}
@@ -192,7 +194,7 @@ else {
await foreach (var member in membersEnum)
if (member.TypedContent is RoomMemberEventContent memberEvent)
roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey });
-
+
try {
string? roomIcon = (await room.GetAvatarUrlAsync())?.Url;
if (room is not null)
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
index 2bed22e..686894c 100644
--- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
@@ -115,11 +115,11 @@ else {
// };
// }
// var roomList = new List<RoomInfo>();
- // var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable();
+ // var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncResultEnumerable();
// await foreach (var result in tasks)
// roomList.Add(result);
// return (userProfile, roomList);
- // }).ToAsyncEnumerable();
+ // }).ToAsyncResultEnumerable();
// await foreach (var res in results) {
// dmRooms.Add(new RoomInfo() {
// Room = dmSpaceRoom,
diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
index 79f931b..dd217e9 100644
--- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
@@ -36,7 +36,6 @@
}
//debounce StateHasChanged, we dont want to reredner on every key stroke
-
private CancellationTokenSource _debounceCts = new CancellationTokenSource();
private async Task DebouncedStateHasChanged() {
diff --git a/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor
index e30adf6..17dd554 100644
--- a/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor
+++ b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor
@@ -19,6 +19,7 @@ else {
else if (checkedRooms.Count > 1) {
<p>Done!</p>
}
+
@foreach (var (state, rooms) in matchingStates) {
<u>@state</u>
<br/>
@@ -71,13 +72,14 @@ else {
_semaphoreSlim.Release();
return; //abort if changed
}
+
matchingStates.Clear();
foreach (var homeserver in hss) {
currentHs = homeserver;
var rooms = await homeserver.GetJoinedRooms();
rooms.RemoveAll(x => checkedRooms.Contains(x.RoomId));
checkedRooms.AddRange(rooms.Select(x => x.RoomId));
- var tasks = rooms.Select(x => GetMembershipAsync(x, mxid)).ToAsyncEnumerable();
+ var tasks = rooms.Select(x => GetMembershipAsync(x, mxid)).ToAsyncResultEnumerable();
await foreach (var (room, state) in tasks) {
if (state is null) continue;
if (!matchingStates.ContainsKey(state.Membership))
@@ -97,8 +99,10 @@ else {
return; //abort if changed
}
}
+
StateHasChanged();
}
+
currentHs = null;
StateHasChanged();
_semaphoreSlim.Release();
diff --git a/MatrixUtils.Web/Pages/Rooms/Create.razor b/MatrixUtils.Web/Pages/Rooms/Create.razor
index 021ad18..051d5af 100644
--- a/MatrixUtils.Web/Pages/Rooms/Create.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Create.razor
@@ -12,10 +12,10 @@
@* <pre Contenteditable="true" @onkeypress="@JsonChanged" content="JsonString">@JsonString</pre> *@
<style>
- table.table-top-first-tr tr td:first-child {
- vertical-align: top;
- }
- </style>
+ table.table-top-first-tr tr td:first-child {
+ vertical-align: top;
+ }
+</style>
<table class="table-top-first-tr">
<tr style="padding-bottom: 16px;">
<td>Preset:</td>
@@ -41,7 +41,10 @@
}
else {
<FancyTextBox @bind-Value="@creationEvent.Name"></FancyTextBox>
- <p>(#<FancyTextBox @bind-Value="@creationEvent.RoomAliasName"></FancyTextBox>:@Homeserver.WhoAmI.UserId.Split(':').Last())</p>
+ <p>(#
+ <FancyTextBox @bind-Value="@creationEvent.RoomAliasName"></FancyTextBox>
+ :@Homeserver.WhoAmI.UserId.Split(':').Last())
+ </p>
}
</td>
</tr>
@@ -89,7 +92,8 @@
<td>
@* <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> *@
<div style="display: inline-block; vertical-align: middle;">
- <FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox><br/>
+ <FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox>
+ <br/>
<InputFile OnChange="RoomIconFilePicked"></InputFile>
</div>
</td>
@@ -105,19 +109,27 @@
<FancyTextBox Formatter="@GetPermissionFriendlyName"
Value="@_event"
ValueChanged="val => { creationEvent.PowerLevelContentOverride.Events.ChangeKey(_event, val); }">
- </FancyTextBox>:
+ </FancyTextBox>
+ :
</td>
<td>
- <input type="number" value="@creationEvent.PowerLevelContentOverride.Events[_event]" @oninput="val => { creationEvent.PowerLevelContentOverride.Events[_event] = int.Parse(val.Value.ToString()); }" @onfocusout="() => { creationEvent.PowerLevelContentOverride.Events = creationEvent.PowerLevelContentOverride.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"/>
+ <input type="number" value="@creationEvent.PowerLevelContentOverride.Events[_event]"
+ @oninput="val => { creationEvent.PowerLevelContentOverride.Events[_event] = int.Parse(val.Value.ToString()); }"
+ @onfocusout="() => { creationEvent.PowerLevelContentOverride.Events = creationEvent.PowerLevelContentOverride.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"/>
</td>
</tr>
}
@foreach (var user in creationEvent.PowerLevelContentOverride.Users.Keys) {
var _user = user;
<tr>
- <td><FancyTextBox Value="@_user" ValueChanged="val => { creationEvent.PowerLevelContentOverride.Users.ChangeKey(_user, val); creationEvent.PowerLevelContentOverride.Users = creationEvent.PowerLevelContentOverride.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"></FancyTextBox>:</td>
<td>
- <input type="number" value="@creationEvent.PowerLevelContentOverride.Users[_user]" @oninput="val => { creationEvent.PowerLevelContentOverride.Users[_user] = int.Parse(val.Value.ToString()); }"/>
+ <FancyTextBox Value="@_user"
+ ValueChanged="val => { creationEvent.PowerLevelContentOverride.Users.ChangeKey(_user, val); creationEvent.PowerLevelContentOverride.Users = creationEvent.PowerLevelContentOverride.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"></FancyTextBox>
+ :
+ </td>
+ <td>
+ <input type="number" value="@creationEvent.PowerLevelContentOverride.Users[_user]"
+ @oninput="val => { creationEvent.PowerLevelContentOverride.Users[_user] = int.Parse(val.Value.ToString()); }"/>
</td>
</tr>
}
@@ -266,6 +278,7 @@
var instance = (IRoomCreationTemplate)Activator.CreateInstance(x);
Presets[instance.Name] = instance.CreateRoomRequest;
}
+
Presets = Presets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
if (!Presets.ContainsKey("Default")) {
@@ -299,7 +312,7 @@
private void InviteMember(string mxid) {
if (!creationEvent.InitialState.Any(x => x.Type == "m.room.member" && x.StateKey == mxid) && Homeserver.UserId != mxid)
- creationEvent.InitialState.Add(new StateEvent {
+ creationEvent.InitialState.Add(new MatrixEvent {
Type = "m.room.member",
StateKey = mxid,
TypedContent = new RoomMemberEventContent {
@@ -316,7 +329,7 @@
"m.room.server_acl" => "Server ACL",
"m.room.avatar" => "Avatar",
_ => key
- };
+ };
private string GetPermissionFriendlyName(string key) => key switch {
"m.reaction" => "Send reaction",
@@ -331,6 +344,6 @@
"m.room.pinned_events" => "Pin events",
"m.room.server_acl" => "Change server ACLs",
_ => key
- };
+ };
}
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index 412d8c9..f2ab186 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -5,22 +5,26 @@
@using System.Diagnostics
@using LibMatrix.RoomTypes
@using System.Collections.Frozen
+@using System.Collections.Immutable
@using System.Reflection
@using System.Text.Json
@using ArcaneLibs.Attributes
+@using ArcaneLibs.Blazor.Components.Services
@using LibMatrix.EventTypes
@using LibMatrix.EventTypes.Interop.Draupnir
@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using SpawnDev.BlazorJS.WebWorkers
@using MatrixUtils.Web.Pages.Rooms.PolicyListComponents
+@using SpawnDev.BlazorJS
@inject WebWorkerService WebWorkerService
@inject ILogger<PolicyList> logger
+@inject BlazorJSRuntime JsRuntime
@if (!IsInitialised) {
<p>Connecting to homeserver...</p>
}
else {
- <PolicyListEditorHeader Room="@Room" ReloadStateAsync="@(() => LoadStateAsync(true))"></PolicyListEditorHeader>
+ <PolicyListEditorHeader Room="@Room" @bind-RenderEventInfo="@RenderEventInfo" ReloadStateAsync="@(() => LoadStateAsync(true))"></PolicyListEditorHeader>
@if (Loading) {
<p>Loading...</p>
}
@@ -39,14 +43,36 @@ else {
</p>
}
+ @if (DuplicateBans?.ActivePolicies.Count > 0) {
+ <p style="color: orange;">
+ Found @DuplicateBans.Value.ActivePolicies.Count duplicate bans
+ </p>
+ }
+
+ @if (RedundantBans?.ActivePolicies.Count > 0) {
+ <p style="color: orange;">
+ Found @RedundantBans.Value.ActivePolicies.Count redundant bans
+ </p>
+ }
+
// logger.LogInformation($"Rendered header in {renderSw.GetElapsedAndRestart()}");
// var renderSw2 = Stopwatch.StartNew();
// IOrderedEnumerable<Type> policiesByType = KnownPolicyTypes.Where(t => GetPolicyEventsByType(t).Count > 0).OrderByDescending(t => GetPolicyEventsByType(t).Count);
// logger.LogInformation($"Ordered policy types by count in {renderSw2.GetElapsedAndRestart()}");
+ @if (DuplicateBans?.ActivePolicies.Count > 0) {
+ <PolicyListCategoryComponent RenderInvalidSection="false" RenderEventInfo="@RenderEventInfo" PolicyCollection="@DuplicateBans.Value"
+ Room="@Room"></PolicyListCategoryComponent>
+ }
+
+ @if (RedundantBans?.ActivePolicies.Count > 0) {
+ <PolicyListCategoryComponent RenderInvalidSection="false" RenderEventInfo="@RenderEventInfo" PolicyCollection="@RedundantBans.Value"
+ Room="@Room"></PolicyListCategoryComponent>
+ }
+
foreach (var collection in PolicyCollections.Values.OrderByDescending(x => x.ActivePolicies.Count)) {
- <PolicyListCategoryComponent PolicyCollection="@collection" Room="@Room"></PolicyListCategoryComponent>
+ <PolicyListCategoryComponent RenderInvalidSection="false" RenderEventInfo="@RenderEventInfo" PolicyCollection="@collection" Room="@Room"></PolicyListCategoryComponent>
}
// foreach (var type in policiesByType) {
@@ -119,7 +145,7 @@ else {
// }
// logger.LogInformation($"Rendered policies in {renderSw.GetElapsedAndRestart()}");
- logger.LogInformation($"Rendered in {renderTotalSw.Elapsed}");
+ logger.LogInformation("Rendered in {TimeSpan}", renderTotalSw.Elapsed);
}
}
@@ -138,11 +164,17 @@ else {
public required string RoomId { get; set; }
[Parameter, SupplyParameterFromQuery]
- public bool RenderEventInfo { get; set; }
+ public bool RenderEventInfo {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ }
- private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
+ private Dictionary<Type, List<MatrixEventResponse>> PolicyEventsByType { get; set; } = new();
- public StateEventResponse? ServerPolicyToMakePermanent {
+ public MatrixEventResponse? ServerPolicyToMakePermanent {
get;
set {
field = value;
@@ -155,7 +187,20 @@ else {
private RoomPowerLevelEventContent PowerLevels { get; set; } = null!;
public bool CurrentUserIsDraupnir { get; set; }
- public Dictionary<StateEventResponse, int> ActiveKicks { get; set; } = [];
+ public Dictionary<MatrixEventResponse, int> ActiveKicks { get; set; } = [];
+
+ private static FrozenSet<Type> KnownPolicyTypes = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
+
+ // event types, unnamed
+ // private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
+ // .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x);
+ //
+ // private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes
+ // .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray());
+
+ Dictionary<Type, PolicyCollection> PolicyCollections { get; set; } = new();
+ PolicyCollection? DuplicateBans { get; set; }
+ PolicyCollection? RedundantBans { get; set; }
protected override async Task OnInitializedAsync() {
var sw = Stopwatch.StartNew();
@@ -172,18 +217,19 @@ else {
StateHasChanged();
await LoadStateAsync(firstLoad: true);
Loading = false;
- logger.LogInformation($"Policy list editor initialized in {sw.Elapsed}!");
+ logger.LogInformation("Policy list editor initialized in {SwElapsed}!", sw.Elapsed);
}
private async Task LoadStateAsync(bool firstLoad = false) {
// preload workers in task pool
// await Task.WhenAll(Enumerable.Range(0, WebWorkerService.MaxWorkerCount).Select(async _ => (await WebWorkerService.TaskPool.GetWorkerAsync()).WhenReady).ToList());
+ var taskPoolReadyTask = WebWorkerService.TaskPool.SetWorkerCount(WebWorkerService.MaxWorkerCount);
var sw = Stopwatch.StartNew();
// Loading = true;
// var states = Room.GetFullStateAsync();
var states = await Room.GetFullStateAsListAsync();
// PolicyEventsByType.Clear();
- logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}");
+ logger.LogInformation("LoadStatesAsync: Loaded state in {SwElapsed}", sw.Elapsed);
foreach (var type in KnownPolicyTypes) {
if (!PolicyCollections.ContainsKey(type)) {
@@ -197,7 +243,7 @@ else {
var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => props.Any(y => y.Name == x.Name))
.ToFrozenDictionary(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName(), x => x);
- logger.LogInformation($"{proxySafeProps?.Count} proxy safe props found in {type.FullName} ({filterPropSw.Elapsed})");
+ logger.LogInformation("{Count} proxy safe props found in {TypeFullName} ({TimeSpan})", proxySafeProps?.Count, type.FullName, filterPropSw.Elapsed);
PolicyCollections.Add(type, new() {
Name = type.GetFriendlyNamePluralOrNull() ?? type.FullName ?? type.Name,
ActivePolicies = [],
@@ -210,9 +256,11 @@ else {
var count = 0;
var parseSw = Stopwatch.StartNew();
foreach (var evt in states) {
- var sw2 = Stopwatch.StartNew();
var mappedType = evt.MappedType;
- logger.LogInformation($"Processing state #{count++:000000} {evt.Type} @ {sw.Elapsed} (took {parseSw.Elapsed:c} so far to process)");
+ if (count % 100 == 0)
+ logger.LogInformation("Processing state #{Count:000000} {EvtType} @ {SwElapsed} (took {ParseSwElapsed:c} so far to process)", count, evt.Type, sw.Elapsed, parseSw.Elapsed);
+ count++;
+
if (!mappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
var collection = PolicyCollections[mappedType];
@@ -226,34 +274,37 @@ else {
if (evt.RawContent is null or { Count: 0 } || string.IsNullOrWhiteSpace(evt.RawContent?["recommendation"]?.GetValue<string>())) {
collection.ActivePolicies.Remove(key);
if (!collection.RemovedPolicies.TryAdd(key, policyInfo)) {
- if (StateEvent.Equals(collection.RemovedPolicies[key].Policy, evt)) continue;
+ if (MatrixEvent.Equals(collection.RemovedPolicies[key].Policy, evt)) continue;
collection.RemovedPolicies[key] = policyInfo;
}
}
else {
collection.RemovedPolicies.Remove(key);
if (!collection.ActivePolicies.TryAdd(key, policyInfo)) {
- if (StateEvent.Equals(collection.ActivePolicies[key].Policy, evt)) continue;
+ if (MatrixEvent.Equals(collection.ActivePolicies[key].Policy, evt)) continue;
collection.ActivePolicies[key] = policyInfo;
}
}
}
- logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}");
+ logger.LogInformation("LoadStatesAsync: Processed state in {SwElapsed}", sw.Elapsed);
foreach (var collection in PolicyCollections) {
- logger.LogInformation($"Policy collection {collection.Key.FullName} has {collection.Value.ActivePolicies.Count} active and {collection.Value.RemovedPolicies.Count} removed policies.");
+ logger.LogInformation("Policy collection {KeyFullName} has {ActivePoliciesCount} active and {RemovedPoliciesCount} removed policies.", collection.Key.FullName, collection.Value.ActivePolicies.Count, collection.Value.RemovedPolicies.Count);
}
+ await Task.Delay(1);
+
Loading = false;
StateHasChanged();
await Task.Delay(100);
+ // return;
logger.LogInformation("LoadStatesAsync: Scanning for redundant policies...");
var scanSw = Stopwatch.StartNew();
- var allPolicyInfos = PolicyCollections.Values
- .SelectMany(x => x.ActivePolicies.Values)
- .ToList();
+ // var allPolicyInfos = PolicyCollections.Values
+ // .SelectMany(x => x.ActivePolicies.Values)
+ // .ToArray();
// var allPolicies = allPolicyInfos
// .Select<PolicyCollection.PolicyInfo, (PolicyCollection.PolicyInfo PolicyInfo, PolicyRuleEventContent TypedContent)>(x => (x, (x.Policy.TypedContent as PolicyRuleEventContent)!))
// .ToList();
@@ -277,7 +328,7 @@ else {
// foreach (var (policyInfo, policyContent) in allPolicies) {
// foreach (var (otherPolicyInfo, otherPolicyContent) in allPolicies) {
// if (policyInfo.Policy == otherPolicyInfo.Policy) continue; // same event
- // if (StateEvent.TypeKeyPairMatches(policyInfo.Policy, otherPolicyInfo.Policy)) {
+ // if (MatrixEvent.TypeKeyPairMatches(policyInfo.Policy, otherPolicyInfo.Policy)) {
// logger.LogWarning("Sanity check failed: Found same type and state key for two different policies: {Policy1} and {Policy2}", policyInfo.Policy.RawContent.ToJson(), otherPolicyInfo.Policy.RawContent.ToJson());
// continue; // same type and state key
// }
@@ -290,28 +341,71 @@ else {
// }
// }
- Console.WriteLine($"Scanning for redundant policies in {allPolicyInfos.Count} total policies... ({scanSw.Elapsed})");
+ int scanningPolicyCount = 0;
+ var aggregatedPolicies = PolicyCollections.Values
+ .Aggregate(new List<MatrixEventResponse>(), (acc, val) => {
+ acc.AddRange(val.ActivePolicies.Select(x => x.Value.Policy));
+ return acc;
+ });
+ Console.WriteLine($"Scanning for redundant policies in {aggregatedPolicies.Count} total policies... ({scanSw.Elapsed})");
List<Task<List<PolicyCollection.PolicyInfo>>> tasks = [];
// try to save some load...
- var policiesJson = JsonSerializer.Serialize(allPolicyInfos.Select(x => x.Policy));
- var ranges = Enumerable.Range(0, allPolicyInfos.Count).DistributeSequentially(WebWorkerService.MaxWorkerCount);
- foreach (var range in ranges)
- tasks.Add(WebWorkerService.TaskPool.Invoke(CheckDuplicatePoliciesAsync, policiesJson, range.First(), range.Last()));
-
+ var policiesJson = JsonSerializer.Serialize(aggregatedPolicies);
+ var policiesJsonMarshalled = JsRuntime.ReturnMe<SpawnDev.BlazorJS.JSObjects.String>(policiesJson);
+ var ranges = Enumerable.Range(0, aggregatedPolicies.Count).DistributeSequentially(WebWorkerService.MaxWorkerCount);
+ await taskPoolReadyTask;
+ tasks.AddRange(ranges.Select(range => WebWorkerService.TaskPool.Invoke(CheckDuplicatePoliciesAsync, policiesJsonMarshalled, range.First(), range.Last())));
+
+ Console.WriteLine($"Main: started {tasks.Count} workers in {scanSw.Elapsed}");
// tasks.Add(CheckDuplicatePoliciesAsync(allPolicyInfos, range.First() .. range.Last()));
- await foreach (var modifiedPolicyInfos in tasks.ToAsyncEnumerable()) {
- Console.WriteLine($"Main: got {modifiedPolicyInfos.Count} modified policies from worker, time: {scanSw.Elapsed}");
+ // var allPolicyEvents = aggregatedPolicies.Select(x => x.Policy).ToList();
+
+ DuplicateBans = new() {
+ Name = "Duplicate bans",
+ ViewType = PolicyCollection.SpecialViewType.Duplicates,
+ ActivePolicies = [],
+ RemovedPolicies = [],
+ PropertiesToDisplay = PolicyCollections.SelectMany(x => x.Value.PropertiesToDisplay).DistinctBy(x => x.Key).ToFrozenDictionary()
+ };
+
+ RedundantBans = new() {
+ Name = "Redundant bans",
+ ViewType = PolicyCollection.SpecialViewType.Redundant,
+ ActivePolicies = [],
+ RemovedPolicies = [],
+ PropertiesToDisplay = PolicyCollections.SelectMany(x => x.Value.PropertiesToDisplay).DistinctBy(x => x.Key).ToFrozenDictionary()
+ };
+
+ var allPolicyInfos = PolicyCollections.Values
+ .SelectMany(x => x.ActivePolicies.Values)
+ .ToArray();
+
+ await foreach (var modifiedPolicyInfos in tasks.ToAsyncResultEnumerable()) {
+ if (modifiedPolicyInfos.Count == 0) continue;
+ var applySw = Stopwatch.StartNew();
+ // Console.WriteLine($"Main: got {modifiedPolicyInfos.Count} modified policies from worker, time: {scanSw.Elapsed}");
foreach (var modifiedPolicyInfo in modifiedPolicyInfos) {
var original = allPolicyInfos.First(p => p.Policy.EventId == modifiedPolicyInfo.Policy.EventId);
- original.DuplicatedBy = modifiedPolicyInfo.DuplicatedBy;
- original.MadeRedundantBy = modifiedPolicyInfo.MadeRedundantBy;
+ original.DuplicatedBy = aggregatedPolicies.Where(x => modifiedPolicyInfo.DuplicatedBy.Any(y => MatrixEvent.Equals(x, y))).ToList();
+ original.MadeRedundantBy = aggregatedPolicies.Where(x => modifiedPolicyInfo.MadeRedundantBy.Any(y => MatrixEvent.Equals(x, y))).ToList();
+ modifiedPolicyInfo.DuplicatedBy = modifiedPolicyInfo.MadeRedundantBy = []; // Early dereference
+ if (original.DuplicatedBy.Count > 0) {
+ if (!DuplicateBans.Value.ActivePolicies.ContainsKey((original.Policy.Type, original.Policy.StateKey!)))
+ DuplicateBans.Value.ActivePolicies.Add((original.Policy.Type, original.Policy.StateKey!), original);
+ }
+
+ if (original.MadeRedundantBy.Count > 0) {
+ if (!RedundantBans.Value.ActivePolicies.ContainsKey((original.Policy.Type, original.Policy.StateKey!)))
+ RedundantBans.Value.ActivePolicies.Add((original.Policy.Type, original.Policy.StateKey!), original);
+ }
+ // Console.WriteLine($"Memory usage: {Util.BytesToString(GC.GetTotalMemory(false))}");
}
- Console.WriteLine($"Processed {modifiedPolicyInfos.Count} modified policies in {scanSw.Elapsed}");
+ Console.WriteLine($"Main: Processed {modifiedPolicyInfos.Count} modified policies in {scanSw.Elapsed} (applied in {applySw.Elapsed})");
}
- Console.WriteLine($"Processed {allPolicyInfos.Count} policies in {scanSw.Elapsed}");
+ Console.WriteLine($"Processed {allPolicyInfos.Length} policies in {scanSw.Elapsed}");
// // scan for wildcard matches
// foreach (var policy in allPolicies) {
@@ -381,17 +475,27 @@ else {
}
[return: WorkerTransfer]
+ private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(SpawnDev.BlazorJS.JSObjects.String policiesJson, int start, int end) {
+ var policies = JsonSerializer.Deserialize<List<MatrixEventResponse>>(policiesJson.ValueOf());
+ Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.ValueOf().Length} bytes of JSON ({policies!.Count} policies)");
+ return await CheckDuplicatePoliciesAsync(policies!, start .. end);
+ }
+
+ [return: WorkerTransfer]
private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(string policiesJson, int start, int end) {
- Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.Length} bytes of JSON");
- return await CheckDuplicatePoliciesAsync(JsonSerializer.Deserialize<List<StateEventResponse>>(policiesJson), start .. end);
+ var policies = JsonSerializer.Deserialize<List<MatrixEventResponse>>(policiesJson);
+ Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.Length} bytes of JSON ({policies!.Count} policies)");
+ return await CheckDuplicatePoliciesAsync(policies!, start .. end);
}
[return: WorkerTransfer]
- private static Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<StateEventResponse> policies, int start, int end)
+ private static Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<MatrixEventResponse> policies, int start, int end)
=> CheckDuplicatePoliciesAsync(policies, start .. end);
[return: WorkerTransfer]
- private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<StateEventResponse> policies, Range range) {
+ private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<MatrixEventResponse> policies, Range range) {
+ var sw = Stopwatch.StartNew();
+ var jsConsole = App.Host.Services.GetService<JsConsoleService>()!;
Console.WriteLine($"Processing policies in range {range} ({range.GetOffsetAndLength(policies.Count).Length}) with {policies.Count} total policies");
var allPolicies = policies
.Select(x => (Event: x, TypedContent: (x.TypedContent as PolicyRuleEventContent)!))
@@ -400,20 +504,33 @@ else {
var modifiedPolicies = new List<PolicyCollection.PolicyInfo>();
foreach (var (policyEvent, policyContent) in toCheck) {
- List<StateEventResponse> duplicatedBy = [];
- List<StateEventResponse> madeRedundantBy = [];
+ List<MatrixEventResponse> duplicatedBy = [];
+ List<MatrixEventResponse> madeRedundantBy = [];
foreach (var (otherPolicyEvent, otherPolicyContent) in allPolicies) {
if (policyEvent == otherPolicyEvent) continue; // same event
- if (StateEvent.TypeKeyPairMatches(policyEvent, otherPolicyEvent)) {
+ if (MatrixEvent.TypeKeyPairMatches(policyEvent, otherPolicyEvent)) {
// logger.LogWarning("Sanity check failed: Found same type and state key for two different policies: {Policy1} and {Policy2}", policyInfo.Policy.RawContent.ToJson(), otherPolicyInfo.Policy.RawContent.ToJson());
Console.WriteLine($"Sanity check failed: Found same type and state key for two different policies: {policyEvent.RawContent.ToJson()} and {otherPolicyEvent.RawContent.ToJson()}");
continue; // same type and state key
}
+
// if(!policyContent.IsHashedRule())
+ if (!string.IsNullOrWhiteSpace(policyContent.Entity) && policyContent.Entity == otherPolicyContent.Entity) {
+ // Console.WriteLine($"Found duplicate policy: {policyEvent.EventId} is duplicated by {otherPolicyEvent.EventId}");
+ duplicatedBy.Add(otherPolicyEvent);
+ }
}
if (duplicatedBy.Count > 0 || madeRedundantBy.Count > 0) {
+ var summary = $"Policy {policyEvent.EventId} is:";
+ if (duplicatedBy.Count > 0)
+ summary += $"\n- Duplicated by {duplicatedBy.Count} policies: {string.Join(", ", duplicatedBy.Select(x => x.EventId))}";
+ if (madeRedundantBy.Count > 0)
+ summary += $"\n- Made redundant by {madeRedundantBy.Count} policies: {string.Join(", ", madeRedundantBy.Select(x => x.EventId))}";
+ // Console.WriteLine(summary);
+ await jsConsole.Info(summary);
+ await Task.Delay(1);
modifiedPolicies.Add(new() {
Policy = policyEvent,
DuplicatedBy = duplicatedBy,
@@ -424,6 +541,8 @@ else {
// await Task.Delay(1);
}
+ await jsConsole.Info($"Worker: Found {modifiedPolicies.Count} modified policies in range {range} (length: {range.GetOffsetAndLength(policies.Count).Length}) in {sw.Elapsed}");
+
return modifiedPolicies;
}
@@ -437,12 +556,12 @@ else {
var states = await Room.GetFullStateAsListAsync();
// PolicyEventsByType.Clear();
- logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}");
+ logger.LogInformation("LoadStatesAsync: Loaded state in {SwElapsed}", sw.Elapsed);
foreach (var type in KnownPolicyTypes) {
if (!PolicyEventsByType.ContainsKey(type))
PolicyEventsByType.Add(type, new List
- <StateEventResponse>(16000));
+ <MatrixEventResponse>(16000));
}
int count = 0;
@@ -454,16 +573,16 @@ else {
e1 = _spsw.Elapsed;
var targetPolicies = PolicyEventsByType[state.MappedType];
e2 = _spsw.Elapsed;
- if (!firstLoad && targetPolicies.FirstOrDefault(x => StateEvent.TypeKeyPairMatches(x, state)) is { } evt) {
+ if (!firstLoad && targetPolicies.FirstOrDefault(x => MatrixEvent.TypeKeyPairMatches(x, state)) is { } evt) {
e3 = _spsw.Elapsed;
- if (StateEvent.Equals(evt, state)) {
+ if (MatrixEvent.Equals(evt, state)) {
if (count % 100 == 0) {
await Task.Delay(10);
await Task.Yield();
}
e4 = _spsw.Elapsed;
- logger.LogInformation($"[E] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={e3:c}, e4={e4:c}, e5={TimeSpan.Zero:c},t={_spsw.Elapsed:c})");
+ logger.LogInformation("[E] LoadStatesAsync: Processed state #{I:000000} {StateType} @ {SwElapsed} (e1={TimeSpan:c}, e2={E2:c}, e3={E3:c}, e4={E4:c}, e5={Zero:c},t={SpswElapsed:c})", count++, state.Type, sw.Elapsed, e1, e2, e3, e4, TimeSpan.Zero, _spsw.Elapsed);
continue;
}
@@ -473,36 +592,36 @@ else {
targetPolicies.Add(state);
e6 = _spsw.Elapsed;
t = _spsw.Elapsed;
- logger.LogInformation($"[M] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={e3:c}, e4={e4:c}, e5={e5:c}, e6={e6:c},t={t:c})");
+ logger.LogInformation("[M] LoadStatesAsync: Processed state #{I:000000} {StateType} @ {SwElapsed} (e1={TimeSpan:c}, e2={E2:c}, e3={E3:c}, e4={E4:c}, e5={E5:c}, e6={E6:c},t={TimeSpan1:c})", count++, state.Type, sw.Elapsed, e1, e2, e3, e4, e5, e6, t);
}
else {
targetPolicies.Add(state);
t = _spsw.Elapsed;
- logger.LogInformation($"[N] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={TimeSpan.Zero:c}, e4={TimeSpan.Zero:c}, e5={TimeSpan.Zero:c}, e6={TimeSpan.Zero:c}, t={t:c})");
+ logger.LogInformation("[N] LoadStatesAsync: Processed state #{I:000000} {StateType} @ {SwElapsed} (e1={TimeSpan:c}, e2={E2:c}, e3={Zero:c}, e4={TimeSpan1:c}, e5={Zero1:c}, e6={TimeSpan2:c}, t={TimeSpan3:c})", count++, state.Type, sw.Elapsed, e1, e2, TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero, t);
}
// await Task.Delay(10);
// await Task.Yield();
}
- logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}");
+ logger.LogInformation("LoadStatesAsync: Processed state in {SwElapsed}", sw.Elapsed);
Loading = false;
StateHasChanged();
await Task.Delay(10);
await Task.Yield();
- logger.LogInformation($"LoadStatesAsync: yield finished in {sw.Elapsed}");
+ logger.LogInformation("LoadStatesAsync: yield finished in {SwElapsed}", sw.Elapsed);
}
- private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
+ private List<MatrixEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
- // private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // private List<MatrixEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
// .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
//
- // private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // private List<MatrixEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
// .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
//
- // private List<StateEventResponse> GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // private List<MatrixEventResponse> GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
// .Where(x => x.RawContent is null or { Count: 0 }).ToList();
private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull()
@@ -511,31 +630,27 @@ else {
private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name;
- private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
-
- // event types, unnamed
- private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
- .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x);
-
- private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes
- .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray());
-
- Dictionary<Type, PolicyCollection> PolicyCollections { get; set; } = new();
-
public struct PolicyCollection {
public required string Name { get; init; }
+ public SpecialViewType ViewType { get; init; }
public int TotalCount => ActivePolicies.Count + RemovedPolicies.Count;
public required Dictionary<(string Type, string StateKey), PolicyInfo> ActivePolicies { get; set; }
- // public Dictionary<(string Type, string StateKey), StateEventResponse> InvalidPolicies { get; set; }
+ // public Dictionary<(string Type, string StateKey), MatrixEventResponse> InvalidPolicies { get; set; }
public required Dictionary<(string Type, string StateKey), PolicyInfo> RemovedPolicies { get; set; }
public required FrozenDictionary<string, PropertyInfo> PropertiesToDisplay { get; set; }
public class PolicyInfo {
- public required StateEventResponse Policy { get; init; }
- public required List<StateEventResponse> MadeRedundantBy { get; set; }
- public required List<StateEventResponse> DuplicatedBy { get; set; }
+ public required MatrixEventResponse Policy { get; init; }
+ public required List<MatrixEventResponse> MadeRedundantBy { get; set; }
+ public required List<MatrixEventResponse> DuplicatedBy { get; set; }
+ }
+
+ public enum SpecialViewType {
+ None,
+ Duplicates,
+ Redundant,
}
}
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs
index 0106c6e..6f45041 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs
@@ -8,12 +8,11 @@ using SpawnDev.BlazorJS.WebWorkers;
namespace MatrixUtils.Web.Pages.Rooms;
public partial class PolicyList {
-
#region Draupnir interop
private SemaphoreSlim ss = new(16, 16);
- private async Task DraupnirKickMatching(StateEventResponse policy) {
+ private async Task DraupnirKickMatching(MatrixEventResponse policy) {
try {
var content = policy.TypedContent! as PolicyRuleEventContent;
if (content is null) return;
@@ -94,7 +93,7 @@ public partial class PolicyList {
#region Nasty, nasty internals, please ignore!
private static class NastyInternalsPleaseIgnore {
- public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List<string> roomIds) {
+ public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, MatrixEventResponse evt, List<string> roomIds) {
try {
// var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList();
var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList();
@@ -131,7 +130,7 @@ public partial class PolicyList {
}
}
- private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) {
+ private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, MatrixEventResponse policy) {
Console.WriteLine($"Checking {roomId}...");
Console.WriteLine(policy.EventId);
}
@@ -140,5 +139,4 @@ public partial class PolicyList {
#endregion
#endregion
-
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
index 5d5bb5d..ac918a8 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
@@ -15,7 +15,11 @@
<h3>Policy list editor - Editing @RoomId</h3>
<hr/>
@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
-<LinkButton OnClickAsync="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
+<LinkButton OnClickAsync="@(() => {
+ CurrentlyEditingEvent = new() { Type = "", RawContent = new() };
+ return Task.CompletedTask;
+ })">Create new policy
+</LinkButton>
@if (Loading) {
<p>Loading...</p>
@@ -71,14 +75,22 @@ else {
}
<div style="display: ruby;">
@if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) {
- <LinkButton OnClickAsync="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton>
+ <LinkButton OnClickAsync="@(() => {
+ CurrentlyEditingEvent = policy;
+ return Task.CompletedTask;
+ })">Edit
+ </LinkButton>
<LinkButton OnClickAsync="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
@if (policy.IsLegacyType) {
<LinkButton OnClickAsync="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
}
@if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) {
- <LinkButton OnClickAsync="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton>
+ <LinkButton OnClickAsync="@(() => {
+ ServerPolicyToMakePermanent = policy;
+ return Task.CompletedTask;
+ })">Make permanent (wildcard)
+ </LinkButton>
@if (CurrentUserIsDraupnir) {
<LinkButton OnClickAsync="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton>
}
@@ -144,12 +156,12 @@ else {
public string RoomId { get; set; }
private bool _enableAvatars;
- private StateEventResponse? _currentlyEditingEvent;
- private StateEventResponse? _serverPolicyToMakePermanent;
+ private MatrixEventResponse? _currentlyEditingEvent;
+ private MatrixEventResponse? _serverPolicyToMakePermanent;
- private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
+ private Dictionary<Type, List<MatrixEventResponse>> PolicyEventsByType { get; set; } = new();
- private StateEventResponse? CurrentlyEditingEvent {
+ private MatrixEventResponse? CurrentlyEditingEvent {
get => _currentlyEditingEvent;
set {
_currentlyEditingEvent = value;
@@ -157,7 +169,7 @@ else {
}
}
- private StateEventResponse? ServerPolicyToMakePermanent {
+ private MatrixEventResponse? ServerPolicyToMakePermanent {
get => _serverPolicyToMakePermanent;
set {
_serverPolicyToMakePermanent = value;
@@ -197,12 +209,12 @@ else {
StateHasChanged();
}
- private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
+ private List<MatrixEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
- private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ private List<MatrixEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
.Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
- private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ private List<MatrixEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
.Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull()
@@ -211,24 +223,24 @@ else {
private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name;
- private async Task RemovePolicyAsync(StateEventResponse policyEvent) {
+ private async Task RemovePolicyAsync(MatrixEventResponse policyEvent) {
await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), new { });
PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent);
await LoadStatesAsync();
}
- private async Task UpdatePolicyAsync(StateEventResponse policyEvent) {
+ private async Task UpdatePolicyAsync(MatrixEventResponse policyEvent) {
await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), policyEvent.RawContent);
CurrentlyEditingEvent = null;
await LoadStatesAsync();
}
- private async Task UpgradePolicyAsync(StateEventResponse policyEvent) {
+ private async Task UpgradePolicyAsync(MatrixEventResponse policyEvent) {
policyEvent.RawContent["upgraded_from_type"] = policyEvent.Type;
await LoadStatesAsync();
}
- private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
+ private static FrozenSet<Type> KnownPolicyTypes = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
// event types, unnamed
private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
index b52e03f..932e0fe 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
@@ -10,43 +10,45 @@
<table class="table table-striped table-hover table-bordered align-middle">
<thead>
<tr>
+ <th>Actions</th>
@foreach (var name in PolicyCollection.PropertiesToDisplay!.Keys) {
<th>@name</th>
}
- <th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue<string>())) {
- <PolicyListRowComponent RenderEventInfo="RenderEventInfo" PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent>
+ <PolicyListRowComponent PolicyCollectionStateHasChanged="@StateHasChanged" RenderEventInfo="RenderEventInfo" PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent>
}
</tbody>
</table>
- <details>
- <summary>
- <u>
- @("Invalid " + PolicyCollection.Name.ToLower())
- </u>
- </summary>
- <table class="table table-striped table-hover table-bordered align-middle">
- <thead>
- <tr>
- <th>State key</th>
- <th>Json contents</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policy in PolicyCollection.RemovedPolicies.Values) {
+ @if (RenderInvalidSection) {
+ <details>
+ <summary>
+ <u>
+ @("Invalid " + PolicyCollection.Name.ToLower())
+ </u>
+ </summary>
+ <table class="table table-striped table-hover table-bordered align-middle">
+ <thead>
<tr>
- <td>@policy.Policy.StateKey</td>
- <td>
- <pre>@policy.Policy.RawContent.ToJson(true, false)</pre>
- </td>
+ <th>State key</th>
+ <th>Json contents</th>
</tr>
- }
- </tbody>
- </table>
- </details>
+ </thead>
+ <tbody>
+ @foreach (var policy in PolicyCollection.RemovedPolicies.Values) {
+ <tr>
+ <td>@policy.Policy.StateKey</td>
+ <td>
+ <pre>@policy.Policy.RawContent.ToJson(true, false)</pre>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+ }
</details>
@code {
@@ -60,6 +62,9 @@
[Parameter]
public bool RenderEventInfo { get; set; }
+ [Parameter]
+ public bool RenderInvalidSection { get; set; } = true;
+
protected override bool ShouldRender() {
// if (PolicyCollection is null) return false;
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
index e82f17d..b57beae 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
@@ -12,14 +12,14 @@
<hr/>
@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
<LinkButton OnClickAsync="@(() => {
- CurrentlyEditingEvent = new() { Type = "", RawContent = new() };
- return Task.CompletedTask;
- })">Create new policy
+ CurrentlyEditingEvent = new() { Type = "", RawContent = new() };
+ return Task.CompletedTask;
+ })">Create new policy
</LinkButton>
<LinkButton OnClickAsync="@(() => {
- MassCreatePolicies = true;
- return Task.CompletedTask;
- })">Create many new policies
+ MassCreatePolicies = true;
+ return Task.CompletedTask;
+ })">Create many new policies
</LinkButton>
<LinkButton OnClickAsync="@(() => ReloadStateAsync())">Refresh</LinkButton>
@@ -33,20 +33,43 @@
// _ = LoadStatesAsync();
})"></MassPolicyEditorModal>
}
+<br/>
+<InputCheckbox Value="@RenderEventInfo" ValueChanged="@RenderEventInfoChanged" ValueExpression="@(() => RenderEventInfo)"/>
+<span> Render event info</span>
@code {
+
[Parameter]
public required GenericRoom Room { get; set; }
-
+
[Parameter]
public required Func<Task> ReloadStateAsync { get; set; }
+ [Parameter]
+ public required bool RenderEventInfo { get; set; }
+
+ [Parameter]
+ public required EventCallback<bool> RenderEventInfoChanged { get; set; }
+
private string? RoomName { get; set; }
private string? RoomAlias { get; set; }
private string? DraupnirShortcode { get; set; }
-
- private StateEventResponse? CurrentlyEditingEvent { get; set { field = value; StateHasChanged(); } }
- private bool MassCreatePolicies { get; set { field = value; StateHasChanged(); } }
+
+ private MatrixEventResponse? CurrentlyEditingEvent {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool MassCreatePolicies {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ }
protected override async Task OnInitializedAsync() {
await Task.WhenAll(
@@ -54,12 +77,12 @@
Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }),
Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); })
);
-
+
StateHasChanged();
}
- private async Task UpdatePolicyAsync(StateEventResponse evt) {
- Console.WriteLine("UpdatePolicyAsync in PolicyListEditorHeader not yet implementeD!");
+ private async Task UpdatePolicyAsync(MatrixEventResponse evt) {
+ Console.WriteLine("UpdatePolicyAsync in PolicyListEditorHeader not yet implemented!");
}
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
index 9ac5077..3ded78f 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
@@ -1,4 +1,5 @@
@using System.Reflection
+@using ArcaneLibs.Extensions
@using LibMatrix
@using LibMatrix.EventTypes.Spec.State.Policy
@using LibMatrix.RoomTypes
@@ -6,30 +7,6 @@
@if (_isInitialized && IsVisible) {
<tr id="@PolicyInfo.Policy.EventId">
- @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) {
- if (prop.Name == "Entity") {
- <td>
- <span>@TruncateMxid(TypedContent.Entity)</span>
- @foreach (var dup in PolicyInfo.DuplicatedBy) {
- <br/>
- <span>Duplicated by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span>
- }
- @foreach (var dup in PolicyInfo.MadeRedundantBy) {
- <br/>
- <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span>
- }
- @if (RenderEventInfo) {
- <br/>
- <pre>
- @PolicyInfo.Policy.Type/@PolicyInfo.Policy.StateKey by @PolicyInfo.Policy.Sender at @PolicyInfo.Policy.OriginServerTs
- </pre>
- }
- </td>
- }
- else {
- <td>@prop.GetGetMethod()?.Invoke(TypedContent, null)</td>
- }
- }
<td>
<div style="display: flex; flex-direction: row; gap: 0.5em;">
@* @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, Policy.Type)) { *@
@@ -41,7 +18,7 @@
</LinkButton>
<LinkButton OnClickAsync="@RemovePolicyAsync">Remove</LinkButton>
@if (Policy.IsLegacyType) {
- <LinkButton OnClickAsync="@RemovePolicyAsync">Update policy type</LinkButton>
+ <LinkButton OnClickAsync="@RemovePolicyAsync">Update type</LinkButton>
}
@if (TypedContent.Entity?.StartsWith("@*:", StringComparison.Ordinal) == true) {
@@ -66,6 +43,30 @@
}
</div>
</td>
+ @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) {
+ if (prop.Name == "Entity") {
+ <td>
+ <span>@TruncateMxid(TypedContent.Entity)</span>
+ @foreach (var dup in PolicyInfo.DuplicatedBy) {
+ <br/>
+ <span>Duplicated by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span>
+ }
+ @foreach (var dup in PolicyInfo.MadeRedundantBy) {
+ <br/>
+ <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span>
+ }
+ @if (RenderEventInfo) {
+ <br/>
+ <pre style="margin-bottom: unset;">
+ @PolicyInfo.Policy.Type/@PolicyInfo.Policy.StateKey by @PolicyInfo.Policy.Sender at @PolicyInfo.Policy.OriginServerTimestamp
+ </pre>
+ }
+ </td>
+ }
+ else {
+ <td>@prop.GetGetMethod()?.Invoke(TypedContent, null)</td>
+ }
+ }
</tr>
@if (IsEditing) {
@@ -95,7 +96,10 @@
[Parameter]
public bool RenderEventInfo { get; set; }
- private StateEventResponse Policy => PolicyInfo.Policy;
+ [Parameter]
+ public required Action PolicyCollectionStateHasChanged { get; set; }
+
+ private MatrixEventResponse Policy => PolicyInfo.Policy;
private bool IsEditing {
get;
@@ -142,13 +146,47 @@
private async Task RemovePolicyAsync() {
await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, new { });
- IsVisible = false;
- StateHasChanged();
+ bool shouldUpdateVisibility = true;
+ PolicyCollection.ActivePolicies.Remove((Policy.Type, Policy.StateKey));
+ PolicyCollection.RemovedPolicies.Add((Policy.Type, Policy.StateKey), PolicyInfo);
+ if (PolicyInfo.DuplicatedBy.Count > 0) {
+ foreach (var evt in PolicyInfo.DuplicatedBy) {
+ var matchingEntry = PolicyCollection.ActivePolicies
+ .FirstOrDefault(x => MatrixEvent.Equals(x.Value.Policy, evt)).Value;
+ var removals = matchingEntry.DuplicatedBy.RemoveAll(x => MatrixEvent.Equals(x, Policy));
+ Console.WriteLine($"Removed {removals} duplicates from {evt.EventId}, matching entry: {matchingEntry.ToJson()}");
+ if (PolicyCollection.ViewType == PolicyList.PolicyCollection.SpecialViewType.Duplicates && matchingEntry.DuplicatedBy.Count == 0) {
+ PolicyCollection.ActivePolicies.Remove((matchingEntry.Policy.Type, matchingEntry.Policy.StateKey));
+ PolicyCollection.RemovedPolicies.Add((matchingEntry.Policy.Type, matchingEntry.Policy.StateKey), matchingEntry);
+ Console.WriteLine($"Also removed {matchingEntry.Policy.EventId} as it is now redundant");
+ }
+ }
+
+ PolicyCollectionStateHasChanged();
+ shouldUpdateVisibility = false;
+ }
+
+ if (PolicyInfo.MadeRedundantBy.Count > 0) {
+ foreach (var evt in PolicyInfo.MadeRedundantBy) {
+ var matchingEntry = PolicyCollection.ActivePolicies
+ .FirstOrDefault(x => MatrixEvent.Equals(x.Value.Policy, evt)).Value;
+ var removals = matchingEntry.MadeRedundantBy.RemoveAll(x => MatrixEvent.Equals(x, Policy));
+ Console.WriteLine($"Removed {removals} redundants from {evt.EventId}, matching entry: {matchingEntry.ToJson()}");
+ }
+
+ PolicyCollectionStateHasChanged();
+ shouldUpdateVisibility = false;
+ }
+
+ if (shouldUpdateVisibility) {
+ IsVisible = false;
+ StateHasChanged();
+ }
// PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent);
// await LoadStatesAsync();
}
- private async Task UpdatePolicyAsync(StateEventResponse evt) {
+ private async Task UpdatePolicyAsync(MatrixEventResponse evt) {
await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, Policy.RawContent);
// CurrentlyEditingEvent = null;
// await LoadStatesAsync();
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
index 6df56ba..a84ef8c 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
@@ -12,9 +12,9 @@
<h3>
<span>Policy lists </span>
<LinkButton OnClickAsync="@(() => {
- ShowPolicyListCreationWindow = true;
- return Task.CompletedTask;
- })">
+ ShowPolicyListCreationWindow = true;
+ return Task.CompletedTask;
+ })">
<span class="oi oi-plus" aria-hidden="true"> Create</span>
</LinkButton>
</h3>
@@ -137,7 +137,7 @@
if (policies.Count == 0) return null;
Status2 = $"Found legacy list {room.RoomId}...";
return await RoomInfo.FromRoom(room, state, true);
- }).ToAsyncEnumerable();
+ }).ToAsyncResultEnumerable();
await foreach (var room in rooms) {
if (room is not null) {
@@ -145,7 +145,7 @@
StateHasChanged();
}
}
-
+
isLoading = false;
Status = "";
Status2 = "";
@@ -188,7 +188,7 @@
Server
}
- public static async Task<RoomInfo> FromRoom(GenericRoom room, List<StateEventResponse>? state = null, bool legacy = false) {
+ public static async Task<RoomInfo> FromRoom(GenericRoom room, List<MatrixEventResponse>? state = null, bool legacy = false) {
state ??= await room.GetFullStateAsListAsync();
return new RoomInfo() {
Room = room,
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor
index 99facbf..2b1d90a 100644
--- a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor
@@ -5,7 +5,7 @@
<tr>
<td style="vertical-align: top;">Initial room state:</td>
<td>
- @foreach (var (displayName, events) in new Dictionary<string, List<StateEvent>>() {
+ @foreach (var (displayName, events) in new Dictionary<string, List<MatrixEvent>>() {
{ "Important room state (before final access rules)", roomBuilder.ImportantState },
{ "Additional room state (after final access rules)", roomBuilder.InitialState },
}) {
@@ -47,10 +47,10 @@
@* if (string.IsNullOrWhiteSpace(json)) *@
@* events.Remove(initialState); *@
@* else *@
- @* events.Replace(initialState, JsonSerializer.Deserialize<StateEvent>(json)); *@
+ @* events.Replace(initialState, JsonSerializer.Deserialize<MatrixEvent>(json)); *@
@* StateHasChanged(); *@
@* })"></FancyTextBox> *@
- <FancyTextBoxLazyJson T="StateEvent" Value="@initialState" ValueChanged="@(evt => { events.Replace(initialState, evt); })"></FancyTextBoxLazyJson>
+ <FancyTextBoxLazyJson T="MatrixEvent" Value="@initialState" ValueChanged="@(evt => { events.Replace(initialState, evt); })"></FancyTextBoxLazyJson>
<br/>
</div>
}
@@ -71,7 +71,7 @@
[Parameter]
public AuthenticatedHomeserverGeneric Homeserver { get; set; }
- private RenderFragment GetRemoveButton(List<StateEvent> events, StateEvent initialState) {
+ private RenderFragment GetRemoveButton(List<MatrixEvent> events, MatrixEvent initialState) {
return @<span>
<LinkButton InlineText="true" OnClick="@(() => {
events.Remove(initialState);
diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor
index 86a4c13..93df5a9 100644
--- a/MatrixUtils.Web/Pages/Rooms/Space.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Space.razor
@@ -30,7 +30,7 @@
private GenericRoom? Room { get; set; }
- private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>();
+ private MatrixEventResponse[] States { get; set; } = Array.Empty<MatrixEventResponse>();
private List<RoomInfo> Rooms { get; } = new();
private List<string> ServersInSpace { get; } = new();
private string? NewRoomId { get; set; }
@@ -61,6 +61,7 @@
}
});
}
+
break;
}
case "m.room.member": {
@@ -68,44 +69,46 @@
if (!ServersInSpace.Contains(serverName)) {
ServersInSpace.Add(serverName);
}
+
break;
}
}
}
+
await base.OnInitializedAsync();
- // var state = await Room.GetStateAsync("");
- // if (state is not null) {
- // // Console.WriteLine(state.Value.ToJson());
- // States = state.Value.Deserialize<StateEventResponse[]>()!;
- //
- // foreach (var stateEvent in States) {
- // if (stateEvent.Type == "m.space.child") {
- // // if (stateEvent.Content.ToJson().Length < 5) return;
- // var roomId = stateEvent.StateKey;
- // var room = hs.GetRoom(roomId);
- // if (room is not null) {
- // Rooms.Add(room);
- // }
- // }
- // else if (stateEvent.Type == "m.room.member") {
- // var serverName = stateEvent.StateKey.Split(':').Last();
- // if (!ServersInSpace.Contains(serverName)) {
- // ServersInSpace.Add(serverName);
- // }
- // }
- // }
+ // var state = await Room.GetStateAsync("");
+ // if (state is not null) {
+ // // Console.WriteLine(state.Value.ToJson());
+ // States = state.Value.Deserialize<MatrixEventResponse[]>()!;
+ //
+ // foreach (var stateEvent in States) {
+ // if (stateEvent.Type == "m.space.child") {
+ // // if (stateEvent.Content.ToJson().Length < 5) return;
+ // var roomId = stateEvent.StateKey;
+ // var room = hs.GetRoom(roomId);
+ // if (room is not null) {
+ // Rooms.Add(room);
+ // }
+ // }
+ // else if (stateEvent.Type == "m.room.member") {
+ // var serverName = stateEvent.StateKey.Split(':').Last();
+ // if (!ServersInSpace.Contains(serverName)) {
+ // ServersInSpace.Add(serverName);
+ // }
+ // }
+ // }
- // if(state.Value.TryGetProperty("Type", out var Type))
- // {
- // }
- // else
- // {
- // //this is fine, apprently...
- // //Console.WriteLine($"Room {room.RoomId} has no Content.Type in m.room.create!");
- // }
+ // if(state.Value.TryGetProperty("Type", out var Type))
+ // {
+ // }
+ // else
+ // {
+ // //this is fine, apprently...
+ // //Console.WriteLine($"Room {room.RoomId} has no Content.Type in m.room.create!");
+ // }
- // await base.OnInitializedAsync();
+ // await base.OnInitializedAsync();
}
private async Task JoinAllRooms() {
@@ -120,24 +123,25 @@
var room = Room!.Homeserver.GetRoom(roomId);
if (room is null) return;
try {
- await room.JoinAsync(ServersInSpace.ToArray());
+ await room.JoinAsync(ServersInSpace.Take(10).ToArray());
var joined = false;
while (!joined) {
var ce = await room.GetCreateEventAsync();
- if(ce is null) continue;
+ if (ce is null) continue;
if (ce.Type == "m.space") {
- var children = room.AsSpace().GetChildrenAsync(false);
- await foreach (var child in children) {
- JoinRecursive(child.RoomId);
- }
+ var children = room.AsSpace().GetChildrenAsync(false);
+ await foreach (var child in children) {
+ JoinRecursive(child.RoomId);
+ }
}
+
joined = true;
+ await Task.Delay(1000);
}
}
catch (Exception e) {
Console.WriteLine(e);
}
-
}
private async Task AddNewRoom() {
diff --git a/MatrixUtils.Web/Pages/Rooms/StateEditor.razor b/MatrixUtils.Web/Pages/Rooms/StateEditor.razor
index 51cb265..47146bc 100644
--- a/MatrixUtils.Web/Pages/Rooms/StateEditor.razor
+++ b/MatrixUtils.Web/Pages/Rooms/StateEditor.razor
@@ -37,8 +37,8 @@
[Parameter]
public string? RoomId { get; set; }
- public List<StateEventResponse> FilteredEvents { get; set; } = new();
- public List<StateEventResponse> Events { get; set; } = new();
+ public List<MatrixEventResponse> FilteredEvents { get; set; } = new();
+ public List<MatrixEventResponse> Events { get; set; } = new();
public string status = "";
protected override async Task OnInitializedAsync() {
@@ -58,7 +58,7 @@
var StateLoaded = 0;
var response = (hs.GetRoom(RoomId)).GetFullStateAsync();
await foreach (var _ev in response) {
- // var e = new StateEventResponse {
+ // var e = new MatrixEventResponse {
// Type = _ev.Type,
// StateKey = _ev.StateKey,
// OriginServerTs = _ev.OriginServerTs,
@@ -68,6 +68,7 @@
if (string.IsNullOrEmpty(_ev.StateKey)) {
FilteredEvents.Add(_ev);
}
+
StateLoaded++;
if (!((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)) continue;
@@ -103,11 +104,12 @@
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 EventId { get; set; }
- // public string UserId { get; set; }
- // public string ReplacesState { get; set; }
+ // public string Sender { get; set; }
+ // public string EventId { get; set; }
+ // public string UserId { get; set; }
+ // public string ReplacesState { get; set; }
}
public bool ShowMembershipEvents {
diff --git a/MatrixUtils.Web/Pages/Rooms/StateViewer.razor b/MatrixUtils.Web/Pages/Rooms/StateViewer.razor
index c8b87d3..16b1d3d 100644
--- a/MatrixUtils.Web/Pages/Rooms/StateViewer.razor
+++ b/MatrixUtils.Web/Pages/Rooms/StateViewer.razor
@@ -12,20 +12,20 @@
<table class="table table-striped table-hover" style="width: fit-Content;">
<thead>
- <tr>
- <th scope="col">Type</th>
- <th scope="col">Content</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey == "").OrderBy(x => x.OriginServerTs)) {
<tr>
- <td>@stateEvent.Type</td>
- <td style="max-width: fit-Content;">
- <pre>@stateEvent.RawContent.ToJson()</pre>
- </td>
+ <th scope="col">Type</th>
+ <th scope="col">Content</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey == "").OrderBy(x => x.OriginServerTs)) {
+ <tr>
+ <td>@stateEvent.Type</td>
+ <td style="max-width: fit-Content;">
+ <pre>@stateEvent.RawContent.ToJson()</pre>
+ </td>
+ </tr>
+ }
</tbody>
</table>
@@ -34,20 +34,20 @@
<summary>@group.Key</summary>
<table class="table table-striped table-hover" style="width: fit-Content;">
<thead>
- <tr>
- <th scope="col">Type</th>
- <th scope="col">Content</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var stateEvent in group.OrderBy(x => x.OriginServerTs)) {
<tr>
- <td>@stateEvent.Type</td>
- <td style="max-width: fit-Content;">
- <pre>@stateEvent.RawContent.ToJson()</pre>
- </td>
+ <th scope="col">Type</th>
+ <th scope="col">Content</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var stateEvent in group.OrderBy(x => x.OriginServerTs)) {
+ <tr>
+ <td>@stateEvent.Type</td>
+ <td style="max-width: fit-Content;">
+ <pre>@stateEvent.RawContent.ToJson()</pre>
+ </td>
+ </tr>
+ }
</tbody>
</table>
</details>
@@ -64,8 +64,8 @@
[Parameter]
public string? RoomId { get; set; }
- public List<StateEventResponse> FilteredEvents { get; set; } = new();
- public List<StateEventResponse> Events { get; set; } = new();
+ public List<MatrixEventResponse> FilteredEvents { get; set; } = new();
+ public List<MatrixEventResponse> Events { get; set; } = new();
public string status = "";
protected override async Task OnInitializedAsync() {
@@ -88,6 +88,7 @@
if (string.IsNullOrEmpty(_ev.StateKey)) {
FilteredEvents.Add(_ev);
}
+
StateLoaded++;
if (!((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)) continue;
diff --git a/MatrixUtils.Web/Pages/Rooms/Timeline.razor b/MatrixUtils.Web/Pages/Rooms/Timeline.razor
index 2af819b..f9137b0 100644
--- a/MatrixUtils.Web/Pages/Rooms/Timeline.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Timeline.razor
@@ -22,7 +22,7 @@
public string RoomId { get; set; }
private List<TimelineEventItem> Events { get; } = new();
- private List<StateEventResponse> RawEvents { get; } = new();
+ private List<MatrixEventResponse> RawEvents { get; } = new();
private AuthenticatedHomeserverGeneric? Homeserver { get; set; }
@@ -44,9 +44,9 @@
await base.OnInitializedAsync();
}
- // private StateEventResponse GetProfileEventBefore(StateEventResponse Event) => Events.TakeWhile(x => x != Event).Last(e => e.Type == RoomMemberEventContent.EventId && e.StateKey == Event.Sender);
+ // private MatrixEventResponse GetProfileEventBefore(MatrixEventResponse Event) => Events.TakeWhile(x => x != Event).Last(e => e.Type == RoomMemberEventContent.EventId && e.StateKey == Event.Sender);
- private Type ComponentType(StateEvent Event) => Event.Type switch {
+ private Type ComponentType(MatrixEvent Event) => Event.Type switch {
RoomCanonicalAliasEventContent.EventId => typeof(TimelineCanonicalAliasItem),
RoomHistoryVisibilityEventContent.EventId => typeof(TimelineHistoryVisibilityItem),
RoomTopicEventContent.EventId => typeof(TimelineRoomTopicItem),
@@ -57,9 +57,9 @@
// RoomMessageReactionEventContent.EventId => typeof(ComponentBase),
_ => typeof(TimelineUnknownItem)
};
-
+
private class TimelineEventItem : ComponentBase {
- public StateEventResponse Event { get; set; }
+ public MatrixEventResponse Event { get; set; }
public Type Type { get; set; }
}
diff --git a/MatrixUtils.Web/Pages/StreamTest.razor b/MatrixUtils.Web/Pages/StreamTest.razor
index 8b9735e..949bddc 100644
--- a/MatrixUtils.Web/Pages/StreamTest.razor
+++ b/MatrixUtils.Web/Pages/StreamTest.razor
@@ -48,9 +48,9 @@
var members = roomState.Where(x => x.Type == RoomMemberEventContent.EventId).ToList();
Console.WriteLine($"Got {members.Count()} members");
var ss = new SemaphoreSlim(1, 1);
- foreach (var stateEventResponse in members) {
- // Console.WriteLine(stateEventResponse.ToJson());
- var mc = stateEventResponse.TypedContent as RoomMemberEventContent;
+ foreach (var MatrixEventResponse in members) {
+ // Console.WriteLine(MatrixEventResponse.ToJson());
+ var mc = MatrixEventResponse.TypedContent as RoomMemberEventContent;
if (!string.IsNullOrWhiteSpace(mc?.AvatarUrl)) {
var uri = mc.AvatarUrl[6..].Split('/');
var url = $"/_matrix/media/v3/download/{uri[0]}/{uri[1]}";
@@ -89,7 +89,7 @@
// var res2 = Homeserver.ClientHttpClient.GetAsync(url);
// var tasks = Enumerable.Range(1, 128)
// .Select(x => Homeserver.ClientHttpClient.GetStreamAsync(url+$"?width={x*128}&height={x*128}"))
- // .ToAsyncEnumerable();
+ // .ToAsyncResultEnumerable();
await foreach (var result in GetStreamsDelayed(url)) {
Streams.Add(result);
// await Task.Delay(100);
@@ -107,7 +107,7 @@
for (int i = 0; i < 32; i++) {
var tasks = Enumerable.Range(1, 4)
.Select(x => Homeserver.ClientHttpClient.GetStreamAsync(url + $"?width={x * 128}&height={x * 128}&r={Random.Shared.Next(100000)}"))
- .ToAsyncEnumerable();
+ .ToAsyncResultEnumerable();
await foreach (var result in tasks) {
yield return result;
}
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
index b0f7dbf..067036e 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
@@ -53,8 +53,8 @@
var oldRoom = hs.GetRoom(roomId);
var newRoom = hs.GetRoom(newRoomId);
var members = await oldRoom.GetMembersListAsync();
- var tasks = members.Select(x => ExecuteInvite(hs, newRoom, x.StateKey)).ToAsyncEnumerable();
- // var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable();
+ var tasks = members.Select(x => ExecuteInvite(hs, newRoom, x.StateKey)).ToAsyncResultEnumerable();
+ // var tasks = hss.Select(ExecuteInvite).ToAsyncResultEnumerable();
await foreach (var a in tasks) {
if (!string.IsNullOrWhiteSpace(a)) {
log.Add(a);
diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor
index 4a44753..a0abcd4 100644
--- a/MatrixUtils.Web/Pages/Tools/Index.razor
+++ b/MatrixUtils.Web/Pages/Tools/Index.razor
@@ -12,6 +12,7 @@
<a href="/Tools/User/MassRoomJoin">Join room across all session</a><br/>
<a href="/Tools/User/CopyPowerlevel">Copy highest powerlevel across all session</a><br/>
<a href="/Tools/User/ViewAccountData">View account data</a><br/>
+<a href="/Tools/User/StickerManager">Manage custom stickers and emojis</a><br/>
<h4 class="tool-category">Room tools</h4>
<hr/>
diff --git a/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
index acad827..8ba160a 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
@@ -41,13 +41,13 @@
var ss = new SemaphoreSlim(32, 32);
var rooms = await hs.GetJoinedRooms();
RoomCount = rooms.Count;
- var fetchTasks = rooms.Select(roomId => workerService.TaskPool.Invoke(() => InternalGetMembersByHomeserver(hs.WellKnownUris.Client, hs.AccessToken, roomId.RoomId))).ToList().ToAsyncEnumerable();
+ var fetchTasks = rooms.Select(roomId => workerService.TaskPool.Invoke(() => InternalGetMembersByHomeserver(hs.WellKnownUris.Client, hs.AccessToken, roomId.RoomId))).ToList().ToAsyncResultEnumerable();
// var fetchTasks = rooms.Select(async x => {
- // await ss.WaitAsync();
- // var res = await x.GetMembersByHomeserverAsync();
- // ss.Release();
- // return res;
- // }).ToAsyncEnumerable();
+ // await ss.WaitAsync();
+ // var res = await x.GetMembersByHomeserverAsync();
+ // ss.Release();
+ // return res;
+ // }).ToAsyncResultEnumerable();
await foreach (var result in fetchTasks) {
foreach (var (resHomeserver, resMembers) in result) {
if (!homeservers.TryAdd(resHomeserver, resMembers)) {
diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
index f8d1d31..ba8036c 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
@@ -74,7 +74,7 @@ else
}
//use timeline
- var types = StateEventResponse.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent)));
+ var types = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent)));
var filter = new SyncFilter.EventFilter(types: types.SelectMany(x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName)).ToList());
var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 2500, filter: filter.ToJson(indent: false, ignoreNull: true));
await foreach (var response in timeline) {
@@ -83,7 +83,7 @@ else
foreach (var message in response.Chunk) {
if (!message.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
//OriginServerTs to datetime
- var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)message.OriginServerTs!.Value).DateTime;
+ var dt = DateTimeOffset.FromUnixTimeMilliseconds(message.OriginServerTs!.Value).DateTime;
var date = new DateOnly(dt.Year, dt.Month, dt.Day);
if (!RoomData[roomName].ContainsKey(date.Year)) {
RoomData[roomName][date.Year] = new();
@@ -100,9 +100,8 @@ else
else rgb.B++;
RoomData[roomName][date.Year][date] = rgb;
}
-
-
}
+
var max = RoomData.SelectMany(x => x.Value.Values).Aggregate(new ActivityGraph.RGB(), (current, next) => new() {
R = Math.Max(current.R, next.Average(x => x.Value.R)),
G = Math.Max(current.G, next.Average(x => x.Value.G)),
@@ -120,10 +119,10 @@ else
private readonly struct StateEventEntry {
public required DateTime Timestamp { get; init; }
public required StateEventTransition State { get; init; }
- public required StateEventResponse Event { get; init; }
- public required StateEventResponse? Previous { get; init; }
+ public required MatrixEventResponse Event { get; init; }
+ public required MatrixEventResponse? Previous { get; init; }
- public void Deconstruct(out StateEventTransition transition, out StateEventResponse evt, out StateEventResponse? prev) {
+ public void Deconstruct(out StateEventTransition transition, out MatrixEventResponse evt, out MatrixEventResponse? prev) {
transition = State;
evt = Event;
prev = Previous;
diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
index fcdb3d0..76ff629 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
@@ -11,7 +11,8 @@
<p>Users: </p>
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
<br/>
-<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClickAsync="@DoImportFromRoomId">Import from room (ID)</LinkButton>
+<InputText @bind-Value="@ImportFromRoomId"></InputText>
+<LinkButton OnClickAsync="@DoImportFromRoomId">Import from room (ID)</LinkButton>
<details>
<summary>Rooms to be searched (@rooms.Count)</summary>
@@ -44,9 +45,7 @@
@foreach (var (userId, events) in matches) {
<p>
<span>@userId.PadRight(col1Width)</span>
- @foreach (var @event in events) {
-
-}
+ @foreach (var @event in events) { }
</p>
}
</pre>
@@ -61,7 +60,7 @@
private ObservableCollection<string> log { get; set; } = new();
List<AuthenticatedHomeserverGeneric> hss { get; set; } = new();
ObservableCollection<GenericRoom> rooms { get; set; } = new();
- Dictionary<GenericRoom, FrozenSet<StateEventResponse>> roomMembers { get; set; } = new();
+ Dictionary<GenericRoom, FrozenSet<MatrixEventResponse>> roomMembers { get; set; } = new();
Dictionary<string, List<Matches>> matches = new();
private string UserIdString {
@@ -97,7 +96,7 @@
rooms = new ObservableCollection<GenericRoom>(distinctRooms);
rooms.CollectionChanged += (sender, args) => StateHasChanged();
- var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync())).ToAsyncEnumerable();
+ var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync())).ToAsyncResultEnumerable();
await foreach (var (room, state) in stateTasks) {
roomMembers.Add(room, state);
@@ -148,7 +147,7 @@
private class Matches {
public GenericRoom Room;
- public StateEventResponse Event;
+ public MatrixEventResponse Event;
// public
}
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
index 1fd0ff6..9139561 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
@@ -145,7 +145,7 @@
private class Match {
public GenericRoom Room;
- public StateEventResponse Event;
+ public MatrixEventResponse Event;
public string RoomName { get; set; }
}
@@ -161,7 +161,7 @@
}
return null;
- }).ToAsyncEnumerable();
+ }).ToAsyncResultEnumerable();
await foreach (var result in results) {
if (result is not null) {
yield return result;
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
index 11c4a80..ec1d190 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
@@ -31,23 +31,23 @@
</p>
<p>
<LinkButton OnClickAsync="@(async () => {
- ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false;
- StateHasChanged();
- })">Hide all
+ ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false;
+ StateHasChanged();
+ })">Hide all
</LinkButton>
<LinkButton OnClickAsync="@(async () => {
- ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true;
- StateHasChanged();
- })">Show all
+ ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true;
+ StateHasChanged();
+ })">Show all
</LinkButton>
<LinkButton OnClickAsync="@(async () => {
- ShowJoins ^= true;
- ShowLeaves ^= true;
- ShowKnocks ^= true;
- ShowInvites ^= true;
- ShowBans ^= true;
- StateHasChanged();
- })">Toggle all
+ ShowJoins ^= true;
+ ShowLeaves ^= true;
+ ShowKnocks ^= true;
+ ShowInvites ^= true;
+ ShowBans ^= true;
+ StateHasChanged();
+ })">Toggle all
</LinkButton>
</p>
<p>
@@ -56,25 +56,25 @@
<span><InputCheckbox @bind-Value="DisambiguateKicks"/> kicks</span>
<span><InputCheckbox @bind-Value="DisambiguateUnbans"/> unbans</span>
<span><InputCheckbox @bind-Value="DisambiguateProfileUpdates"/> profile updates</span>
- <details style="display: inline-block; vertical-align: top;">
- <summary>
- <InputCheckbox @bind-Value="DisambiguateInviteActions"/>
- invite actions
- </summary>
- <span><InputCheckbox @bind-Value="DisambiguateInviteAccepted"/> accepted</span>
- <span><InputCheckbox @bind-Value="DisambiguateInviteRejected"/> rejected</span>
- <span><InputCheckbox @bind-Value="DisambiguateInviteRetracted"/> retracted</span>
- </details>
- <details style="display: inline-block; vertical-align: top;">
- <summary>
- <InputCheckbox @bind-Value="DisambiguateKnockActions"/>
- knock actions
- </summary>
- <span><InputCheckbox @bind-Value="DisambiguateKnockAccepted"/> accepted</span>
- <span><InputCheckbox @bind-Value="DisambiguateKnockRejected"/> rejected</span>
- <span><InputCheckbox @bind-Value="DisambiguateKnockRetracted"/> retracted</span>
- </details>
- }
+ <details style="display: inline-block; vertical-align: top;">
+ <summary>
+ <InputCheckbox @bind-Value="DisambiguateInviteActions"/>
+ invite actions
+ </summary>
+ <span><InputCheckbox @bind-Value="DisambiguateInviteAccepted"/> accepted</span>
+ <span><InputCheckbox @bind-Value="DisambiguateInviteRejected"/> rejected</span>
+ <span><InputCheckbox @bind-Value="DisambiguateInviteRetracted"/> retracted</span>
+ </details>
+ <details style="display: inline-block; vertical-align: top;">
+ <summary>
+ <InputCheckbox @bind-Value="DisambiguateKnockActions"/>
+ knock actions
+ </summary>
+ <span><InputCheckbox @bind-Value="DisambiguateKnockAccepted"/> accepted</span>
+ <span><InputCheckbox @bind-Value="DisambiguateKnockRejected"/> rejected</span>
+ <span><InputCheckbox @bind-Value="DisambiguateKnockRetracted"/> retracted</span>
+ </details>
+}
</p>
@if (DoDisambiguate) {
<p>
@@ -130,29 +130,29 @@
<p>
<LinkButton OnClickAsync="@(async () => {
- DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false;
- StateHasChanged();
- })">Un-disambiguate all
+ DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false;
+ StateHasChanged();
+ })">Un-disambiguate all
</LinkButton>
<LinkButton OnClickAsync="@(async () => {
- DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true;
- StateHasChanged();
- })">Disambiguate all
+ DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true;
+ StateHasChanged();
+ })">Disambiguate all
</LinkButton>
<LinkButton OnClickAsync="@(async () => {
- DisambiguateProfileUpdates ^= true;
- DisambiguateKicks ^= true;
- DisambiguateUnbans ^= true;
- DisambiguateInviteAccepted ^= true;
- DisambiguateInviteRejected ^= true;
- DisambiguateInviteRetracted ^= true;
- DisambiguateKnockAccepted ^= true;
- DisambiguateKnockRejected ^= true;
- DisambiguateKnockRetracted ^= true;
- DisambiguateKnockActions ^= true;
- DisambiguateInviteActions ^= true;
- StateHasChanged();
- })">Toggle all
+ DisambiguateProfileUpdates ^= true;
+ DisambiguateKicks ^= true;
+ DisambiguateUnbans ^= true;
+ DisambiguateInviteAccepted ^= true;
+ DisambiguateInviteRejected ^= true;
+ DisambiguateInviteRetracted ^= true;
+ DisambiguateKnockAccepted ^= true;
+ DisambiguateKnockRejected ^= true;
+ DisambiguateKnockRetracted ^= true;
+ DisambiguateKnockActions ^= true;
+ DisambiguateInviteActions ^= true;
+ StateHasChanged();
+ })">Toggle all
</LinkButton>
</p>
}
@@ -306,18 +306,61 @@
private bool ShowBans { get; set; } = true;
private bool DoDisambiguate { get; set; } = true;
- private bool DisambiguateProfileUpdates { get => field && DoDisambiguate; set; } = true;
- private bool DisambiguateKicks { get => field && DoDisambiguate; set; } = true;
- private bool DisambiguateUnbans { get => field && DoDisambiguate; set; } = true;
- private bool DisambiguateInviteAccepted { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true;
- private bool DisambiguateInviteRejected { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true;
- private bool DisambiguateInviteRetracted { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true;
- private bool DisambiguateKnockAccepted { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true;
- private bool DisambiguateKnockRejected { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true;
- private bool DisambiguateKnockRetracted { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true;
-
- private bool DisambiguateKnockActions { get => field && DoDisambiguate; set; } = true;
- private bool DisambiguateInviteActions { get => field && DoDisambiguate; set; } = true;
+
+ private bool DisambiguateProfileUpdates {
+ get => field && DoDisambiguate;
+ set;
+ } = true;
+
+ private bool DisambiguateKicks {
+ get => field && DoDisambiguate;
+ set;
+ } = true;
+
+ private bool DisambiguateUnbans {
+ get => field && DoDisambiguate;
+ set;
+ } = true;
+
+ private bool DisambiguateInviteAccepted {
+ get => field && DoDisambiguate && DisambiguateInviteActions;
+ set;
+ } = true;
+
+ private bool DisambiguateInviteRejected {
+ get => field && DoDisambiguate && DisambiguateInviteActions;
+ set;
+ } = true;
+
+ private bool DisambiguateInviteRetracted {
+ get => field && DoDisambiguate && DisambiguateInviteActions;
+ set;
+ } = true;
+
+ private bool DisambiguateKnockAccepted {
+ get => field && DoDisambiguate && DisambiguateKnockActions;
+ set;
+ } = true;
+
+ private bool DisambiguateKnockRejected {
+ get => field && DoDisambiguate && DisambiguateKnockActions;
+ set;
+ } = true;
+
+ private bool DisambiguateKnockRetracted {
+ get => field && DoDisambiguate && DisambiguateKnockActions;
+ set;
+ } = true;
+
+ private bool DisambiguateKnockActions {
+ get => field && DoDisambiguate;
+ set;
+ } = true;
+
+ private bool DisambiguateInviteActions {
+ get => field && DoDisambiguate;
+ set;
+ } = true;
private bool ShowProfileUpdates {
get => field && DisambiguateProfileUpdates;
@@ -399,7 +442,7 @@
#endregion
private ObservableCollection<string> Log { get; set; } = new();
- private List<StateEventResponse> Memberships { get; set; } = [];
+ private List<MatrixEventResponse> Memberships { get; set; } = [];
private AuthenticatedHomeserverGeneric Homeserver { get; set; }
[Parameter, SupplyParameterFromQuery(Name = "room")]
@@ -444,10 +487,10 @@
private readonly struct MembershipEntry {
public required MembershipTransition State { get; init; }
- public required StateEventResponse Event { get; init; }
- public required StateEventResponse? Previous { get; init; }
+ public required MatrixEventResponse Event { get; init; }
+ public required MatrixEventResponse? Previous { get; init; }
- public void Deconstruct(out MembershipTransition transition, out StateEventResponse evt, out StateEventResponse? prev) {
+ public void Deconstruct(out MembershipTransition transition, out MatrixEventResponse evt, out MatrixEventResponse? prev) {
transition = State;
evt = Event;
prev = Previous;
@@ -474,7 +517,7 @@
KnockRetracted
}
- private static IEnumerable<MembershipEntry> GetTransitions(List<StateEventResponse> evts) {
+ private static IEnumerable<MembershipEntry> GetTransitions(List<MatrixEventResponse> evts) {
Dictionary<string, MembershipEntry> transitions = new();
foreach (var evt in evts.OrderBy(x => x.OriginServerTs)) {
var content = evt.TypedContent as RoomMemberEventContent ?? throw new InvalidOperationException("Event is not a RoomMemberEventContent!");
@@ -528,7 +571,7 @@
{ MembershipTransition.KnockRejected, MembershipTransition.Leave },
{ MembershipTransition.KnockRetracted, MembershipTransition.Leave }
}.ToFrozenDictionary();
-
+
foreach (var entry in entries) {
if (!DoDisambiguate) {
yield return entry;
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
index ee77532..a8ae603 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
@@ -55,7 +55,7 @@
<td>@sets.Item2[0].Room.RoomId</td>
<td>@((sets.Item2[i].Member.TypedContent as RoomMemberEventContent).Membership)</td>
<td>@(roomNames.ContainsKey(sets.Item2[i].Room) ? roomNames[sets.Item2[i].Room] : "")</td>
- <td>@(roomAliasses.ContainsKey(sets.Item2[i].Room) ? roomAliasses[sets.Item2[i].Room] : "")</td>
+ <td>@(roomAliasses.ContainsKey(sets.Item2[i].Room) ? roomAliasses[sets.Item2[i].Room] : "")</td>
}
else {
<td/>
@@ -88,7 +88,7 @@
[Parameter, SupplyParameterFromQuery(Name = "b")]
public string ImportSetBSpaceId { get; set; } = "";
- Dictionary<string, Dictionary<GenericRoom, StateEventResponse>> roomMembers { get; set; } = new();
+ Dictionary<string, Dictionary<GenericRoom, MatrixEventResponse>> roomMembers { get; set; } = new();
Dictionary<string, (List<Match>, List<Match>)> matches { get; set; } = new();
@@ -127,7 +127,7 @@
var setBusers = new Dictionary<string, List<Match>>();
await Task.WhenAll(GetMembers(RoomsA, setAusers), GetMembers(RoomsB, setBusers));
-
+
Log.Add($"Got {setAusers.Count} users in set A");
Log.Add($"Got {setBusers.Count} users in set B");
Log.Add("Calculating intersections...");
@@ -191,7 +191,7 @@
public class Match {
public GenericRoom Room { get; set; }
- public StateEventResponse Member { get; set; }
+ public MatrixEventResponse Member { get; set; }
}
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
index f39a2eb..d160922 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
@@ -26,10 +26,10 @@
<details>
<summary>Results</summary>
- @foreach (var (userId, events) in matches.OrderBy(x=>x.Key)) {
+ @foreach (var (userId, events) in matches.OrderBy(x => x.Key)) {
<h4>@userId</h4>
<table>
- @foreach (var match in events.OrderBy(x=>x.RoomName)) {
+ @foreach (var match in events.OrderBy(x => x.RoomName)) {
<tr>
<td>@match.RoomName (<span>@match.Room.RoomId</span>)</td>
<td>
@@ -139,7 +139,7 @@
private class Match {
public GenericRoom Room;
- public StateEventResponse Event;
+ public MatrixEventResponse Event;
public string RoomName { get; set; }
}
@@ -161,7 +161,7 @@
}
return null;
- }).ToAsyncEnumerable();
+ }).ToAsyncResultEnumerable();
await foreach (var result in results) {
if (result is not null) {
yield return result;
@@ -169,7 +169,7 @@
}
}
- public string SummarizeMembership(StateEventResponse state) {
+ public string SummarizeMembership(MatrixEventResponse state) {
var membership = state.ContentAs<RoomMemberEventContent>();
var time = DateTimeOffset.FromUnixTimeMilliseconds(state.OriginServerTs!.Value);
return membership switch {
@@ -186,10 +186,9 @@
_ => $"Unknown membership {membership.Membership}, sent at {time} by {state.Sender} for {membership.Reason}"
};
}
-
+
private async Task ExportJson() {
var json = matches.ToJson();
-
}
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Room/SpacePermissions.razor b/MatrixUtils.Web/Pages/Tools/Room/SpacePermissions.razor
new file mode 100644
index 0000000..a47d7f5
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Room/SpacePermissions.razor
@@ -0,0 +1,204 @@
+@page "/Tools/Room/SpacePermissions"
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.RoomTypes
+@using MatrixUtils.Web.Pages.Rooms
+<h3>Space Permissions</h3>
+<hr/>
+<span>Space ID: </span>
+<FancyTextBox @bind-Value="@SpaceId"/>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
+<br/>
+<InputCheckbox @bind-Value="@AutoRecurseSpaces"/>
+<span> Auto-recurse into child spaces</span>
+<br/>
+
+@if (RoomPowerLevels.Count == 0) {
+ <p>No data loaded.</p>
+}
+else {
+ <span>Loaded @LoadedSpaceRooms.Count spaces.</span>
+ <br/>
+ @if (SpaceRooms.Count > 0) {
+ <h3>Load more spaces:</h3>
+ @foreach (var room in SpaceRooms) {
+ <LinkButton OnClickAsync="@(() => LoadSpaceAsync(room.Key))">@room.Value</LinkButton>
+ }
+ }
+
+ <h3>By event type:</h3>
+ <table class="table-striped table-hover table-bordered align-middle">
+ <thead>
+ <td>Room</td>
+ @foreach (var key in OrderedEventTypes) {
+ <td>@key.Key
+ <br/>
+ ~ @Math.Round(key.Value, 2)
+ </td>
+ }
+ </thead>
+ <tbody>
+ @foreach (var (roomName, powerLevels) in RoomPowerLevels.OrderByDescending(x => x.Value.Events!.Values.Average())) {
+ <tr>
+ <td>@roomName</td>
+ @foreach (var eventType in OrderedEventTypes) {
+ if (!powerLevels.Events!.ContainsKey(eventType.Key)) {
+ <td style="background-color: #ff000044;">-</td>
+ continue;
+ }
+
+ <td>@(powerLevels.Events![eventType.Key])</td>
+ }
+ </tr>
+ }
+ </tbody>
+ </table>
+ <br/>
+ <h3>By user:</h3>
+ <table class="table-striped table-hover table-bordered align-middle">
+ <thead>
+ <td>Room</td>
+ @foreach (var key in OrderedUsers) {
+ <td>@key.Key
+ <br/>
+ ~ @Math.Round(key.Value, 2)
+ </td>
+ }
+ </thead>
+ <tbody>
+ @foreach (var (roomName, powerLevels) in RoomPowerLevels.OrderByDescending(x => x.Value.Users!.Values.Average())) {
+ <tr>
+ <td>@roomName</td>
+ @foreach (var eventType in OrderedUsers) {
+ if (!powerLevels.Users!.ContainsKey(eventType.Key)) {
+ <td style="background-color: #ff000044;">-</td>
+ continue;
+ }
+
+ <td>@(powerLevels.Users![eventType.Key])</td>
+ }
+ </tr>
+ }
+ </tbody>
+ </table>
+}
+
+@code {
+
+ [Parameter, SupplyParameterFromQuery]
+ public string? SpaceId { get; set; }
+
+ [Parameter, SupplyParameterFromQuery]
+ public bool AutoRecurseSpaces { get; set; }
+
+ private AuthenticatedHomeserverGeneric? Homeserver { get; set; }
+ private List<AuthenticatedHomeserverGeneric> AllHomeservers { get; set; } = [];
+ private Dictionary<string, List<GenericRoom>> JoinedHomeserversByRoom { get; set; } = [];
+
+ private Dictionary<string, RoomPowerLevelEventContent> RoomPowerLevels { get; set; } = [];
+ private Dictionary<string, string> SpaceRooms { get; set; } = [];
+ private List<string> LoadedSpaceRooms { get; set; } = [];
+
+ private Dictionary<string, double> OrderedEventTypes { get; set; } = new();
+ private Dictionary<string, double> OrderedUsers { get; set; } = new();
+
+ protected override async Task OnInitializedAsync() {
+ if (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true) is not AuthenticatedHomeserverGeneric hs) return;
+ Homeserver = hs;
+ await foreach (var server in sessionStore.TryGetAllHomeservers()) {
+ AllHomeservers.Add(server);
+ var joinedRooms = await server.GetJoinedRooms();
+ foreach (var room in joinedRooms) {
+ if (!JoinedHomeserversByRoom.ContainsKey(room.RoomId)) {
+ JoinedHomeserversByRoom[room.RoomId] = [];
+ }
+
+ JoinedHomeserversByRoom[room.RoomId].Add(room);
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(SpaceId)) {
+ await Execute();
+ }
+ }
+
+ private async Task Execute() {
+ RoomPowerLevels = [];
+ SpaceRooms = [];
+ await LoadSpaceAsync(SpaceId);
+ }
+
+ private async Task<GenericRoom> GetJoinedRoomAsync(string roomId) {
+ var room = Homeserver.GetRoom(roomId);
+ if (await room.IsJoinedAsync()) return room;
+
+ if (JoinedHomeserversByRoom.TryGetValue(roomId, out var rooms)) {
+ foreach (var r in rooms) {
+ if (await r.IsJoinedAsync()) return r;
+ }
+ }
+
+ foreach (var hs in AllHomeservers) {
+ if (hs == Homeserver) continue;
+ room = hs.GetRoom(roomId);
+ if (await room.IsJoinedAsync()) return room;
+ }
+
+ Console.WriteLine($"Not joined to room {roomId} on any known homeserver.");
+ return room; // not null, in case we can preview the room
+ }
+
+ private async Task LoadSpaceAsync(string spaceId) {
+ LoadedSpaceRooms.Add(spaceId);
+ SpaceRooms.Remove(spaceId);
+
+ var space = (await GetJoinedRoomAsync(spaceId)).AsSpace();
+ RoomPowerLevels[await space.GetNameOrFallbackAsync()] = AddFakeEvents(await space.GetPowerLevelsAsync());
+ var children = space.GetChildrenAsync();
+ await foreach (var childRoom in children) {
+ var child = await GetJoinedRoomAsync(childRoom.RoomId);
+ try {
+ var powerlevels = await child.GetPowerLevelsAsync();
+ RoomPowerLevels[await child.GetNameOrFallbackAsync()] = AddFakeEvents(powerlevels!);
+ if (await child.GetRoomType() == SpaceRoom.TypeName) {
+ if (AutoRecurseSpaces)
+ await LoadSpaceAsync(child.RoomId);
+ else
+ SpaceRooms.Add(child.RoomId, await child.GetNameOrFallbackAsync());
+ }
+
+ OrderedEventTypes = RoomPowerLevels
+ .SelectMany(x => x.Value.Events!)
+ .GroupBy(x => x.Key)
+ .ToDictionary(x => x.Key, x => x.Average(y => y.Value))
+ .OrderByDescending(x => x.Value)
+ .ToDictionary(x => x.Key, x => x.Value);
+
+ OrderedUsers = RoomPowerLevels
+ .SelectMany(x => x.Value.Users!)
+ .GroupBy(x => x.Key)
+ .ToDictionary(x => x.Key, x => x.Average(y => y.Value))
+ .OrderByDescending(x => x.Value)
+ .ToDictionary(x => x.Key, x => x.Value);
+ StateHasChanged();
+ }
+ catch (Exception ex) {
+ Console.WriteLine($"Failed to get power levels for room {child.RoomId}: {ex}");
+ }
+ }
+ }
+
+ private RoomPowerLevelEventContent AddFakeEvents(RoomPowerLevelEventContent powerlevels) {
+ powerlevels.Events ??= [];
+ powerlevels.Events["[user_default]"] = powerlevels.UsersDefault ?? 0;
+ powerlevels.Events["[event_default]"] = powerlevels.EventsDefault ?? 0;
+ powerlevels.Events["[state_default]"] = powerlevels.StateDefault ?? 100;
+ powerlevels.Events["[ban]"] = powerlevels.Ban ?? 100;
+ powerlevels.Events["[invite]"] = powerlevels.Invite ?? 100;
+ powerlevels.Events["[kick]"] = powerlevels.Kick ?? 100;
+ powerlevels.Events["[ping_room]"] = powerlevels.NotificationsPl?.Room ?? 100;
+ powerlevels.Events["[redact]"] = powerlevels.Redact ?? 100;
+ return powerlevels;
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
index b893970..acc86a2 100644
--- a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
@@ -42,7 +42,7 @@
private async Task Execute() {
foreach (var hs in hss) {
var rooms = await hs.GetJoinedRooms();
- var tasks = rooms.Select(x=>ApplyPowerlevelsInRoom(hs, x)).ToAsyncEnumerable();
+ var tasks = rooms.Select(x => ApplyPowerlevelsInRoom(hs, x)).ToAsyncResultEnumerable();
await foreach (var a in tasks) {
if (!string.IsNullOrWhiteSpace(a)) {
log.Add(a);
@@ -62,12 +62,11 @@
log.Add("I am same PL in " + room.RoomId);
continue;
}
-
+
pls.SetUserPowerLevel(ahs.WhoAmI.UserId, pls.GetUserPowerLevel(hs.WhoAmI.UserId));
await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls);
log.Add($"Updated powerlevel of {room.RoomId} to {pls.GetUserPowerLevel(ahs.WhoAmI.UserId)}");
}
-
}
catch (MatrixException e) {
return $"Failed to update PLs in {room.RoomId}: {e.Message}";
@@ -75,6 +74,7 @@
catch (Exception e) {
return $"Failed to update PLs in {room.RoomId}: {e.Message}";
}
+
StateHasChanged();
return "";
}
diff --git a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
index 748f2fb..ee17f1d 100644
--- a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
@@ -42,23 +42,24 @@
}
private async Task Execute() {
- // foreach (var hs in hss) {
- // var rooms = await hs.GetJoinedRooms();
- var tasks = hss.Select(ExecuteInvite).ToAsyncEnumerable();
+ // foreach (var hs in hss) {
+ // var rooms = await hs.GetJoinedRooms();
+ var tasks = hss.Select(ExecuteInvite).ToAsyncResultEnumerable();
await foreach (var a in tasks) {
if (!string.IsNullOrWhiteSpace(a)) {
log.Add(a);
StateHasChanged();
}
}
- tasks = hss.Select(ExecuteJoin).ToAsyncEnumerable();
+
+ tasks = hss.Select(ExecuteJoin).ToAsyncResultEnumerable();
await foreach (var a in tasks) {
if (!string.IsNullOrWhiteSpace(a)) {
log.Add(a);
StateHasChanged();
}
}
- // }
+ // }
}
private async Task<string> ExecuteInvite(AuthenticatedHomeserverGeneric hs) {
@@ -69,6 +70,7 @@
if (joinRule.JoinRule == RoomJoinRulesEventContent.JoinRules.Public) return "Room is public, no invite needed";
}
catch { }
+
var pls = await room.GetPowerLevelsAsync();
if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) < pls.Invite) return "I do not have permission to send invite in " + room.RoomId;
await room.InviteUsersAsync(hss.Select(x => x.WhoAmI.UserId).ToList());
@@ -80,6 +82,7 @@
catch (Exception e) {
return $"Failed to invite in {room.RoomId}: {e.Message}";
}
+
StateHasChanged();
return "";
}
@@ -92,6 +95,7 @@
if (mse?.Membership == "join") return $"User {hs.WhoAmI.UserId} already in room";
}
catch { }
+
await room.JoinAsync();
}
catch (MatrixException e) {
@@ -100,6 +104,7 @@
catch (Exception e) {
return $"Failed to join {hs.WhoAmI.UserId} to {room.RoomId}: {e.Message}";
}
+
StateHasChanged();
return "";
}
diff --git a/MatrixUtils.Web/Pages/Tools/User/StickerManager.razor b/MatrixUtils.Web/Pages/Tools/User/StickerManager.razor
new file mode 100644
index 0000000..0e838c7
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/User/StickerManager.razor
@@ -0,0 +1,80 @@
+@page "/Tools/User/StickerManager"
+@using System.Diagnostics
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Common
+@using LibMatrix.EventTypes.Spec
+@inject ILogger<StickerManager> Logger
+<h3>Sticker/emoji manager</h3>
+
+@if (TotalStepsProgress is not null) {
+ <SimpleProgressIndicator ObservableProgress="@TotalStepsProgress"/>
+ <br/>
+}
+@if (_observableProgressState is not null) {
+ <SimpleProgressIndicator ObservableProgress="@_observableProgressState"/>
+ <br/>
+}
+
+@code {
+
+ private AuthenticatedHomeserverGeneric Homeserver { get; set; } = null!;
+ private Msc2545EmoteRoomsAccountDataEventContent? EnabledEmoteRooms { get; set; }
+ private Dictionary<string, StickerRoom> StickerRooms { get; set; } = [];
+
+ private SimpleProgressIndicator.ObservableProgressState? _observableProgressState;
+
+ private SimpleProgressIndicator.ObservableProgressState? TotalStepsProgress { get; set; } = new() {
+ Label = "Authenticating with Matrix...",
+ Max = 2,
+ Value = 0
+ };
+
+ protected override async Task OnInitializedAsync() {
+ if (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true) is not { } hs)
+ return;
+ Homeserver = hs;
+ TotalStepsProgress?.Next("Fetching enabled emote packs...");
+ _ = hs.GetAccountDataOrNullAsync<Msc2545EmoteRoomsAccountDataEventContent>(Msc2545EmoteRoomsAccountDataEventContent.EventId)
+ .ContinueWith(r => {
+ EnabledEmoteRooms = r.Result;
+ StateHasChanged();
+ });
+
+ TotalStepsProgress?.Next("Getting joined rooms...");
+ _observableProgressState = new() {
+ Label = "Loading rooms...",
+ Max = 1,
+ Value = 0
+ };
+ var rooms = await hs.GetJoinedRooms();
+ _observableProgressState.Max.Value = rooms.Count;
+ StateHasChanged();
+
+ var ss = new SemaphoreSlim(32, 32);
+ var ss1 = new SemaphoreSlim(1, 1);
+ var roomScanTasks = rooms.Select(async room => {
+ // await Task.Delay(Random.Shared.Next(100, 1000 + (rooms.Count * 100)));
+ // await ss.WaitAsync();
+ var state = await room.GetFullStateAsListAsync();
+ StickerRoom sr = new();
+ foreach (var evt in state) {
+ if (evt.Type == RoomEmotesEventContent.EventId) { }
+ }
+
+ // ss.Release();
+ // await ss1.WaitAsync();
+ Console.WriteLine("Got state for room " + room.RoomId);
+ // _observableProgressState.Next($"Got state for room {room.RoomId}");
+ // await Task.Delay(1);
+ // ss1.Release();
+ return room.RoomId;
+ })
+ .ToList();
+ await foreach (var roomScanResult in roomScanTasks.ToAsyncResultEnumerable()) {
+ _observableProgressState.Label.Value = roomScanResult;
+ }
+ }
+
+ private class StickerRoom { }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor
index 290b92a..2b7b6cf 100644
--- a/MatrixUtils.Web/Pages/User/Profile.razor
+++ b/MatrixUtils.Web/Pages/User/Profile.razor
@@ -12,9 +12,13 @@
<div>
<MxcAvatar Homeserver="@Homeserver" MxcUri="@NewProfile.AvatarUrl" Circular="true" Size="96"/>
<div style="display: inline-block; vertical-align: middle;">
- <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/>
- <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox>
- <InputFile OnChange="@AvatarChanged"></InputFile><br/>
+ <span>Display name: </span>
+ <FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox>
+ <br/>
+ <span>Avatar URL: </span>
+ <FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox>
+ <InputFile OnChange="@AvatarChanged"></InputFile>
+ <br/>
<LinkButton OnClickAsync="@(() => UpdateProfile())">Update profile</LinkButton>
<LinkButton OnClickAsync="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
</div>
@@ -26,7 +30,9 @@
<br/>
@* <details> *@
- <h4>Room profiles<hr></h4>
+ <h4>Room profiles
+ <hr>
+ </h4>
@foreach (var room in Rooms) {
<details class="details-compact">
@@ -41,9 +47,15 @@
@* <img src="@Homeserver.ResolveMediaUri(room.OwnMembership.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> *@
<MxcAvatar Homeserver="@Homeserver" MxcUri="@room.OwnMembership.AvatarUrl" Circular="true" Size="96"/>
<div style="display: inline-block; vertical-align: middle;">
- <span>Display name: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.DisplayName"></FancyTextBox><br/>
- <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.AvatarUrl"></FancyTextBox>
- <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, room.Room.RoomId))"></InputFile><br/>
+ <span>Display name: </span>
+ <FancyTextBox BackgroundColor="@(room.OwnMembership.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")"
+ @bind-Value="@room.OwnMembership.DisplayName"></FancyTextBox>
+ <br/>
+ <span>Avatar URL: </span>
+ <FancyTextBox BackgroundColor="@(room.OwnMembership.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")"
+ @bind-Value="@room.OwnMembership.AvatarUrl"></FancyTextBox>
+ <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, room.Room.RoomId))"></InputFile>
+ <br/>
<LinkButton OnClickAsync="@(() => UpdateRoomProfile(room.Room.RoomId))">Update profile</LinkButton>
</div>
<br/>
@@ -117,7 +129,7 @@
});
roomInfoTasks.Add(task);
}
-
+
await Task.WhenAll(roomInfoTasks);
StateHasChanged();
@@ -126,7 +138,7 @@
// var roomNameTasks = RoomProfiles.Keys.Select(x => Homeserver.GetRoom(x)).Select(async x => {
// var name = await x.GetNameOrFallbackAsync();
// return new KeyValuePair<string, string?>(x.RoomId, name);
- // }).ToAsyncEnumerable();
+ // }).ToAsyncResultEnumerable();
// await foreach (var (roomId, roomName) in roomNameTasks) {
// Status = $"Got room name for {roomId}: {roomName}";
diff --git a/MatrixUtils.Web/Program.cs b/MatrixUtils.Web/Program.cs
index bc047e8..58b66c1 100644
--- a/MatrixUtils.Web/Program.cs
+++ b/MatrixUtils.Web/Program.cs
@@ -29,7 +29,7 @@ builder.Services.AddWebWorkerService(webWorkerService => {
webWorkerService.TaskPool.MaxPoolSize = -1;
// Below is telling the WebWorkerService TaskPool to set the initial size to 2 if running in a Window scope and 0 otherwise
// This starts up 2 WebWorkers to handle TaskPool tasks as needed
- webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 0 : 0;
+ // webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 0 : 0;
});
try {
@@ -82,5 +82,8 @@ MatrixHttpClient.LogRequests = false;
builder.Services.AddRoryLibMatrixServices();
builder.Services.AddScoped<RmuSessionStore>();
builder.Services.AddSingleton<BlazorSaveFileService>();
+builder.Services.AddSingleton<JsConsoleService>();
+
// await builder.Build().RunAsync();
-await builder.Build().BlazorJSRunAsync();
\ No newline at end of file
+var host = App.Host = builder.Build();
+await host.BlazorJSRunAsync();
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/FilterComponents/BooleanFilterComponent.razor b/MatrixUtils.Web/Shared/FilterComponents/BooleanFilterComponent.razor
new file mode 100644
index 0000000..0730701
--- /dev/null
+++ b/MatrixUtils.Web/Shared/FilterComponents/BooleanFilterComponent.razor
@@ -0,0 +1,17 @@
+@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters
+<span>
+ <InputCheckbox @bind-Value="@Filter.Enabled"/> @Label:
+ @if (Filter.Enabled) {
+ <InputCheckbox @bind-Value="@Filter.Value"/>
+ }
+</span>
+
+@code {
+
+ [Parameter]
+ public required BoolFilter Filter { get; set; }
+
+ [Parameter]
+ public required string Label { get; set; }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/FilterComponents/StringFilterComponent.razor b/MatrixUtils.Web/Shared/FilterComponents/StringFilterComponent.razor
new file mode 100644
index 0000000..c5a6e15
--- /dev/null
+++ b/MatrixUtils.Web/Shared/FilterComponents/StringFilterComponent.razor
@@ -0,0 +1,31 @@
+@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters
+<span style="vertical-align: top;">
+ <InputCheckbox @bind-Value="@Filter.Enabled"/> @Label:
+</span>
+@if (Filter.Enabled) {
+ <div style="display: inline-block;">
+ <InputCheckbox @bind-Value="@Filter.CheckValueContains"/>
+ Contains
+ <FancyTextBox @bind-Value="@Filter.ValueContains"></FancyTextBox>
+ <br/>
+ <InputCheckbox @bind-Value="@Filter.CheckValueEquals"/>
+ Equals
+ <FancyTextBox @bind-Value="@Filter.ValueEquals"></FancyTextBox>
+ <LinkButton OnClick="@SetEqualsNull" InlineText="true"> [Set null]</LinkButton>
+ </div>
+}
+
+@code {
+
+ [Parameter]
+ public required StringFilter Filter { get; set; }
+
+ [Parameter]
+ public required string Label { get; set; }
+
+ private void SetEqualsNull() {
+ Filter.ValueEquals = null;
+ StateHasChanged();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
index bb4b672..b49358d 100644
--- a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
@@ -96,7 +96,7 @@
private bool VerifyIntent { get; set; }
- private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
+ private static FrozenSet<Type> KnownPolicyTypes = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
.ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x);
@@ -177,20 +177,20 @@
// var tasks = entities.Select(x => ExecuteBan(Room, x)).ToList();
// await Task.WhenAll(tasks);
-
+
var events = entities.Select(entity => {
var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent ?? throw new InvalidOperationException("Failed to create event content");
content.Recommendation = Recommendation;
content.Reason = Reason;
content.Entity = entity;
- return new StateEvent() {
+ return new MatrixEvent() {
Type = MappedType,
TypedContent = content,
StateKey = content.GetDraupnir2StateKey()
};
});
-
- foreach(var chunk in events.Chunk(50))
+
+ foreach (var chunk in events.Chunk(50))
await Room.BulkSendEventsAsync(chunk);
OnSaved.Invoke();
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
index 0205e16..501ca99 100644
--- a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
@@ -113,7 +113,7 @@
@code {
[Parameter]
- public StateEventResponse? PolicyEvent {
+ public MatrixEventResponse? PolicyEvent {
get => _policyEvent;
set {
if (value is not null && value != _policyEvent)
@@ -139,10 +139,10 @@
}
[Parameter]
- public Action<StateEventResponse>? OnSave { get; set; }
+ public Action<MatrixEventResponse>? OnSave { get; set; }
[Parameter]
- public Func<StateEventResponse, Task>? OnSaveAsync { get; set; }
+ public Func<MatrixEventResponse, Task>? OnSaveAsync { get; set; }
private async Task InvokeOnSave() {
if (OnSave is not null)
@@ -154,12 +154,12 @@
public PolicyRuleEventContent? PolicyData { get; set; }
- private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
+ private static FrozenSet<Type> KnownPolicyTypes = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
.ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x);
- private StateEventResponse? _policyEvent;
+ private MatrixEventResponse? _policyEvent;
private string? MappedType {
get => _policyEvent?.Type;
diff --git a/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor
index f107eb3..80c69f2 100644
--- a/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor
+++ b/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor
@@ -6,19 +6,19 @@
@code {
[Parameter]
- public StateEventResponse Event { get; set; }
+ public MatrixEventResponse Event { get; set; }
[Parameter]
- public List<StateEventResponse> Events { get; set; }
+ public List<MatrixEventResponse> Events { get; set; }
[Parameter]
public AuthenticatedHomeserverGeneric Homeserver { get; set; }
- public IEnumerable<StateEventResponse> EventsBefore => Events.TakeWhile(e => e.EventId != Event.EventId);
+ public IEnumerable<MatrixEventResponse> EventsBefore => Events.TakeWhile(e => e.EventId != Event.EventId);
- public IEnumerable<StateEventResponse> MatchingEventsBefore => EventsBefore.Where(x => x.Type == Event.Type && x.StateKey == Event.StateKey);
+ public IEnumerable<MatrixEventResponse> MatchingEventsBefore => EventsBefore.Where(x => x.Type == Event.Type && x.StateKey == Event.StateKey);
- public StateEventResponse? PreviousState => MatchingEventsBefore.LastOrDefault();
+ public MatrixEventResponse? PreviousState => MatchingEventsBefore.LastOrDefault();
public RoomMemberEventContent? CurrentSenderMemberEventContent => EventsBefore.LastOrDefault(x => x.Type == "m.room.member" && x.StateKey == Event.Sender)?
.TypedContent as RoomMemberEventContent;
@@ -27,6 +27,4 @@
public bool HasPreviousMessage => EventsBefore.Last() is { Type: "m.room.message" } response && response.Sender == Event.Sender;
-
-
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/appsettings.Development.json b/MatrixUtils.Web/appsettings.Development.json
index 826edbf..1555d4e 100644
--- a/MatrixUtils.Web/appsettings.Development.json
+++ b/MatrixUtils.Web/appsettings.Development.json
@@ -4,6 +4,8 @@
"Default": "Trace",
"System": "Information",
"Microsoft": "Information",
+ "Microsoft.AspNetCore.StaticAssets": "Warning",
+ "Microsoft.AspNetCore.EndpointMiddleware": "Warning",
"ArcaneLibs.Blazor.Components.AuthorizedImage": "Information"
}
}
diff --git a/MatrixUtils.Web/wwwroot/css/app.css b/MatrixUtils.Web/wwwroot/css/app.css
index 3fac9ca..4511b3a 100644
--- a/MatrixUtils.Web/wwwroot/css/app.css
+++ b/MatrixUtils.Web/wwwroot/css/app.css
@@ -1,6 +1,11 @@
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
@import url('jetbrains-mono/jetbrains-mono.css');
+:root {
+ /*--bs-table-hover-bg: rgba(0, 0, 0, 0.75);*/
+ --bs-table-hover-bg: #FF00FF;
+}
+
.avatar48 {
width: 48px;
height: 48px;
diff --git a/MatrixUtils.Web/wwwroot/index.html b/MatrixUtils.Web/wwwroot/index.html
index f25d549..fa233b3 100644
--- a/MatrixUtils.Web/wwwroot/index.html
+++ b/MatrixUtils.Web/wwwroot/index.html
@@ -3,30 +3,33 @@
<head>
<meta charset="utf-8"/>
- <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>MatrixUtils.Web</title>
<base href="/"/>
- <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet"/>
- <link href="css/app.css" rel="stylesheet"/>
+ <link rel="preload" id="webassembly"/>
+ <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css"/>
+ <link rel="stylesheet" href="css/app.css"/>
+ <link rel="icon" type="image/png" href="favicon.png"/>
+ <link href="MatrixUtils.Web.styles.css" rel="stylesheet"/>
<link rel="manifest" href="rmu.webmanifest"/>
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png"/>
- <link href="favicon.png" rel="icon" type="image/png"/>
- <link href="MatrixUtils.Web.styles.css" rel="stylesheet"/>
+ <link rel="apple-touch-icon" sizes="192x192" href="icon-192.png"/>
+ <script type="importmap"></script>
</head>
<body>
<div id="app">
<svg class="loading-progress">
- <circle cx="50%" cy="50%" r="40%"/>
- <circle cx="50%" cy="50%" r="40%"/>
+ <circle r="40%" cx="50%" cy="50%"/>
+ <circle r="40%" cx="50%" cy="50%"/>
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
- <a class="reload" href="">Reload</a>
- <a class="dismiss">🗙</a>
+ <a href="." class="reload">Reload</a>
+ <span class="dismiss">🗙</span>
</div>
<script>
function getWidth(element) {
@@ -49,11 +52,11 @@
}
setImageStream = async (element, imageStream) => {
- if(!(element instanceof HTMLElement)) {
+ if (!(element instanceof HTMLElement)) {
console.error("Element is not an HTMLElement", element);
return;
}
-
+
const arrayBuffer = await imageStream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
@@ -64,8 +67,8 @@
image.src = url;
}
</script>
- <script src="_framework/blazor.webassembly.js"></script>
-<!-- <script>navigator.serviceWorker.register('service-worker.js');</script>-->
+ <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
+ <!-- <script>navigator.serviceWorker.register('service-worker.js');</script>-->
<script src="sw-registrator.js"></script>
</body>
diff --git a/MatrixUtils.Web/wwwroot/service-worker.published.js b/MatrixUtils.Web/wwwroot/service-worker.published.js
index 9219755..3e28e6c 100644
--- a/MatrixUtils.Web/wwwroot/service-worker.published.js
+++ b/MatrixUtils.Web/wwwroot/service-worker.published.js
@@ -9,7 +9,7 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [// Standard resources
- /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /* Extra known-static paths */
+ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/, /* Extra known-static paths */
/\/_matrix\/media\/.{2}\/download\//, /api\.dicebear\.com\/6\.x\/identicon\/svg/];
const offlineAssetsExclude = [/^service-worker\.js$/];
@@ -22,13 +22,13 @@ async function onInstall(event) {
console.info('Service worker: Install');
// Activate the new service worker as soon as the old one is retired.
- self.skipWaiting();
+ await self.skipWaiting();
// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
- .map(asset => new Request(asset.url, {integrity: asset.hash, cache: 'no-cache'}));
+ .map(asset => new Request(asset.url, {cache: 'no-cache'})); /* integrity: asset.hash */
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}
@@ -48,7 +48,8 @@ async function onFetch(event) {
// For all navigation requests, try to serve index.html from cache,
// unless that request is for an offline resource.
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
- const shouldServeIndexHtml = event.request.mode === 'navigate' && !manifestUrlList.some(url => url === event.request.url);
+ const shouldServeIndexHtml = event.request.mode === 'navigate'
+ && !manifestUrlList.some(url => url === event.request.url);
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const shouldCache = offlineAssetsInclude.some(pattern => pattern.test(request.url));
@@ -72,7 +73,7 @@ async function onFetch(event) {
fetched, shouldCache, request, exception, cachedResponse, url: request.url,
}
Object.keys(consoleLog).forEach(key => consoleLog[key] == null && delete consoleLog[key])
- if(consoleLog.exception)
+ if (consoleLog.exception)
console.log("Service worker caching: ", consoleLog)
}
diff --git a/MatrixUtils.Web/wwwroot/sw-registrator.js b/MatrixUtils.Web/wwwroot/sw-registrator.js
index 94b96b2..b57d26a 100644
--- a/MatrixUtils.Web/wwwroot/sw-registrator.js
+++ b/MatrixUtils.Web/wwwroot/sw-registrator.js
@@ -8,14 +8,14 @@ window.updateAvailable = new Promise((resolve, reject) => {
return;
}
- navigator.serviceWorker.register('/service-worker.js')
+ navigator.serviceWorker.register('/service-worker.js', {updateViaCache: 'none'})
.then(registration => {
console.info(`Service worker registration successful (scope: ${registration.scope})`);
// detect updates every minute
setInterval(() => {
registration.update();
- }, 5 * 1000); // 60000ms -> check each minute
+ }, 30 * 1000); // 60000ms -> check each minute
registration.onupdatefound = () => {
const installingServiceWorker = registration.installing;
|