From 75377d795e061349b60eb507b9d2ba292e572ed5 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 18 Dec 2025 16:04:05 +0100 Subject: [PATCH] Add Rust Core WASM scaffolding to AliasVault.Client (#1404) --- apps/server/AliasVault.Client/Program.cs | 2 + .../Services/Database/DbMergeUtility.cs | 162 ---- .../Services/Database/DbService.cs | 241 +++++- .../Services/Native/MergeInput.cs | 28 + .../Services/Native/MergeOutput.cs | 40 + .../Services/Native/MergeStats.cs | 52 ++ .../Services/Native/RustCore.cs | 166 ++++ .../Services/Native/SqlStatement.cs | 28 + .../Services/Native/SyncableTables.cs | 32 + .../Services/Native/TableData.cs | 28 + .../wwwroot/js/rustCoreInterop.js | 175 +++++ .../wwwroot/wasm/aliasvault_core.js | 743 ++++++++++++++++++ .../wwwroot/wasm/aliasvault_core_bg.wasm | Bin 0 -> 291482 bytes .../Models/SystemFieldRegistry.cs | 289 +++++++ core/models/scripts/generate-field-keys.cjs | 500 +++++++++++- core/rust/Cargo.toml | 2 + core/rust/build.sh | 119 ++- core/rust/src/ffi.rs | 181 +++++ core/rust/src/lib.rs | 4 + core/rust/src/merge/mod.rs | 47 -- 20 files changed, 2578 insertions(+), 261 deletions(-) delete mode 100644 apps/server/AliasVault.Client/Services/Database/DbMergeUtility.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/MergeInput.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/MergeOutput.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/MergeStats.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/RustCore.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/SqlStatement.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/SyncableTables.cs create mode 100644 apps/server/AliasVault.Client/Services/Native/TableData.cs create mode 100644 apps/server/AliasVault.Client/wwwroot/js/rustCoreInterop.js create mode 100644 apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core.js create mode 100644 apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm create mode 100644 apps/server/Databases/AliasClientDb/Models/SystemFieldRegistry.cs create mode 100644 core/rust/src/ffi.rs diff --git a/apps/server/AliasVault.Client/Program.cs b/apps/server/AliasVault.Client/Program.cs index df2d9920d..4d656f631 100644 --- a/apps/server/AliasVault.Client/Program.cs +++ b/apps/server/AliasVault.Client/Program.cs @@ -10,6 +10,7 @@ using AliasVault.Client; using AliasVault.Client.Main.Services; using AliasVault.Client.Providers; using AliasVault.Client.Services; +using AliasVault.Client.Services.Native; using AliasVault.RazorComponents.Services; using AliasVault.Shared.Core; using Blazor.WebAssembly.DynamicCulture.Loader; @@ -99,6 +100,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddAuthorizationCore(); builder.Services.AddBlazoredLocalStorage(); diff --git a/apps/server/AliasVault.Client/Services/Database/DbMergeUtility.cs b/apps/server/AliasVault.Client/Services/Database/DbMergeUtility.cs deleted file mode 100644 index f9834d875..000000000 --- a/apps/server/AliasVault.Client/Services/Database/DbMergeUtility.cs +++ /dev/null @@ -1,162 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) aliasvault. All rights reserved. -// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. -// -//----------------------------------------------------------------------- - -namespace AliasVault.Client.Services.Database; - -using System.Globalization; -using Microsoft.Data.Sqlite; - -/// -/// Class with helper methods to merge two or more vaults. -/// -public static class DbMergeUtility -{ - /// - /// Retrieves the names of all tables in the SQLite database. - /// - /// The SQLite connection to use. - /// A list of table names. - /// List of table names. - public static async Task> GetTableNames(SqliteConnection connection) - { - var tables = new List(); - await using var command = connection.CreateCommand(); - command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"; - await using var reader = await command.ExecuteReaderAsync(); - while (await reader.ReadAsync()) - { - tables.Add(reader.GetString(0)); - } - - return tables; - } - - /// - /// Merges data from a source table into a base table. - /// - /// The connection to the base database. - /// The connection to the source database. - /// The name of the table to merge. - /// ILogger instance. - /// Task. - public static async Task MergeTable(SqliteConnection baseConnection, SqliteConnection sourceConnection, string tableName, ILogger logger) - { - await using var baseCommand = baseConnection.CreateCommand(); - await using var sourceCommand = sourceConnection.CreateCommand(); - - baseCommand.CommandText = $"PRAGMA table_info({tableName})"; - var columns = new List(); - - // Get column names from the base table. - await using (var reader = await baseCommand.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - string columnName = reader.GetString(1); - columns.Add(columnName); - } - } - - // Check if the table has Id, UpdatedAt and IsDeleted columns which are required in order to merge. - // If columns are missing, skip the table. - if (!columns.Contains("Id") || !columns.Contains("UpdatedAt") || !columns.Contains("IsDeleted")) - { - return; - } - - // Get all records from the source table. - sourceCommand.CommandText = $"SELECT * FROM {tableName}"; - await using var sourceReader = await sourceCommand.ExecuteReaderAsync(); - - logger.LogDebug("Got records for {tableName}.", tableName); - while (await sourceReader.ReadAsync()) - { - var id = sourceReader.GetValue(0); - var updatedAt = sourceReader.GetDateTime(columns.IndexOf("UpdatedAt")); - - // Check if the record exists in the base table. - baseCommand.CommandText = $"SELECT UpdatedAt FROM {tableName} WHERE Id = @Id"; - baseCommand.Parameters.Clear(); - baseCommand.Parameters.AddWithValue("@Id", id); - - logger.LogDebug("Checking if record exists in {tableName}.", tableName); - - var existingRecord = await baseCommand.ExecuteScalarAsync(); - if (existingRecord != null) - { - logger.LogDebug("Record exists in {tableName}.", tableName); - - // Record exists, compare UpdatedAt if it exists. - logger.LogDebug("Comparing UpdatedAt in {tableName}.", tableName); - logger.LogDebug("UpdatedAt: {existingRecord}", existingRecord); - var baseUpdatedAt = DateTime.Parse((string)existingRecord, CultureInfo.InvariantCulture); - if (updatedAt > baseUpdatedAt) - { - // Source record is newer, update the base record. - await UpdateRecord(baseConnection, tableName, sourceReader, columns); - } - else - { - // Base record is newer, skip. - logger.LogDebug("Base record is newer, skipping {tableName}.", tableName); - } - } - else - { - // Record doesn't exist in base, add it. - await InsertRecord(baseConnection, tableName, sourceReader, columns); - } - } - - logger.LogDebug("Finished merging {tableName}.", tableName); - } - - /// - /// Inserts a new record into the specified table with data from the source reader. - /// - /// The SQLite connection to use. - /// The name of the table to insert into. - /// The data reader containing the source record. - /// The list of column names in the table. - /// Task. - private static async Task InsertRecord(SqliteConnection connection, string tableName, SqliteDataReader sourceReader, List columns) - { - await using var command = connection.CreateCommand(); - var columnNames = string.Join(", ", columns); - var parameterNames = string.Join(", ", columns.Select(c => $"@{c}")); - command.CommandText = $"INSERT INTO {tableName} ({columnNames}) VALUES ({parameterNames})"; - - for (int i = 0; i < columns.Count; i++) - { - command.Parameters.AddWithValue($"@{columns[i]}", sourceReader.GetValue(i)); - } - - await command.ExecuteNonQueryAsync(); - } - - /// - /// Updates a record in the specified table with data from the source reader. - /// - /// The SQLite connection to use. - /// The name of the table to update. - /// The data reader containing the source record. - /// The list of column names in the table. - /// Task. - private static async Task UpdateRecord(SqliteConnection connection, string tableName, SqliteDataReader sourceReader, List columns) - { - await using var command = connection.CreateCommand(); - var updateColumns = string.Join(", ", columns.Select(c => $"{c} = @{c}")); - command.CommandText = $"UPDATE {tableName} SET {updateColumns} WHERE Id = @Id"; - - for (int i = 0; i < columns.Count; i++) - { - command.Parameters.AddWithValue($"@{columns[i]}", sourceReader.GetValue(i)); - } - - await command.ExecuteNonQueryAsync(); - } -} diff --git a/apps/server/AliasVault.Client/Services/Database/DbService.cs b/apps/server/AliasVault.Client/Services/Database/DbService.cs index c73126180..0d0c06f6e 100644 --- a/apps/server/AliasVault.Client/Services/Database/DbService.cs +++ b/apps/server/AliasVault.Client/Services/Database/DbService.cs @@ -9,11 +9,13 @@ namespace AliasVault.Client.Services.Database; using System.Data; using System.Net.Http.Json; +using System.Text.Json; using AliasClientDb; using AliasClientDb.Models; using AliasVault.Client.Services; using AliasVault.Client.Services.Auth; using AliasVault.Client.Services.JsInterop.Models; +using AliasVault.Client.Services.Native; using AliasVault.Client.Utilities; using AliasVault.Shared.Models.Enums; using AliasVault.Shared.Models.WebApi.Vault; @@ -30,6 +32,7 @@ public sealed class DbService : IDisposable private const string _UNKNOWN_VERSION = "Unknown"; private readonly AuthService _authService; private readonly JsInteropService _jsInteropService; + private readonly RustCore _rustCore; private readonly HttpClient _httpClient; private readonly DbServiceState _state = new(); private readonly Config _config; @@ -48,14 +51,16 @@ public sealed class DbService : IDisposable /// /// AuthService. /// JsInteropService. + /// RustCore service for WASM interop. /// HttpClient. /// Config instance. /// Global notification service. /// ILogger instance. - public DbService(AuthService authService, JsInteropService jsInteropService, HttpClient httpClient, Config config, GlobalNotificationService globalNotificationService, ILogger logger) + public DbService(AuthService authService, JsInteropService jsInteropService, RustCore rustCore, HttpClient httpClient, Config config, GlobalNotificationService globalNotificationService, ILogger logger) { _authService = authService; _jsInteropService = jsInteropService; + _rustCore = rustCore; _httpClient = httpClient; _config = config; _globalNotificationService = globalNotificationService; @@ -113,7 +118,7 @@ public sealed class DbService : IDisposable } /// - /// Merges two or more databases into one. + /// Merges two or more databases into one using the Rust WASM merge logic. /// /// Bool which indicates if merging was successful. public async Task MergeDatabasesAsync() @@ -128,51 +133,61 @@ public sealed class DbService : IDisposable return false; } - var sqlConnections = new List(); - _logger.LogInformation("Merging databases..."); + _logger.LogInformation("Merging databases using Rust WASM..."); - // Decrypt and instantiate each vault as a separate in-memory SQLite database. + // Get the list of syncable table names from Rust core. + var tableNames = await _rustCore.GetSyncableTableNamesAsync(); + + // Read local tables as JSON. + var localTables = await ReadTablesAsJsonAsync(_sqlConnection!, tableNames); + _logger.LogDebug("Read {Count} local tables.", localTables.Count); + + // Process each vault to merge. foreach (var vault in vaultsToMerge.Vaults) { // Store username of the loaded vault in memory to send to server as sanity check when updating the vault later. _authService.StoreUsername(vault.Username); var decryptedBase64String = await _jsInteropService.SymmetricDecrypt(vault.Blob, _authService.GetEncryptionKeyAsBase64Async()); - _logger.LogInformation("Decrypted vault {VaultUpdatedAt}.", vault.UpdatedAt); - var connection = new SqliteConnection("Data Source=:memory:"); - await connection.OpenAsync(); - await ImportDbContextFromBase64Async(decryptedBase64String, connection); - sqlConnections.Add(connection); + + // Create a temporary in-memory SQLite database for the server vault. + await using var serverConnection = new SqliteConnection("Data Source=:memory:"); + await serverConnection.OpenAsync(); + await ImportDbContextFromBase64Async(decryptedBase64String, serverConnection); + + // Read server tables as JSON. + var serverTables = await ReadTablesAsJsonAsync(serverConnection, tableNames); + _logger.LogDebug("Read {Count} server tables.", serverTables.Count); + + // Create the merge input. + var mergeInput = new MergeInput + { + LocalTables = localTables, + ServerTables = serverTables, + }; + + // Call Rust WASM merge. + var mergeOutput = await _rustCore.MergeVaultsAsync(mergeInput); + + _logger.LogInformation( + "Merge completed: {TablesProcessed} tables, {FromLocal} kept local, {FromServer} from server, {Inserted} inserted, {Conflicts} conflicts.", + mergeOutput.Stats.TablesProcessed, + mergeOutput.Stats.RecordsFromLocal, + mergeOutput.Stats.RecordsFromServer, + mergeOutput.Stats.RecordsInserted, + mergeOutput.Stats.Conflicts); + + // Execute the SQL statements returned by the merge. + await ExecuteMergeSqlStatementsAsync(mergeOutput.Statements); + + // Update local tables for the next merge iteration (if there are multiple vaults). + localTables = await ReadTablesAsJsonAsync(_sqlConnection!, tableNames); } - // Get all table names from the current base database. - var tables = await DbMergeUtility.GetTableNames(_sqlConnection!); - - // Disable foreign key checks on the base connection. + // Verify foreign key integrity after merge. await using (var command = _sqlConnection!.CreateCommand()) { - command.CommandText = "PRAGMA foreign_keys = OFF;"; - await command.ExecuteNonQueryAsync(); - } - - // Merge every remote database into the current database. - foreach (var connection in sqlConnections) - { - foreach (var table in tables) - { - _logger.LogInformation("Merging table {Table}.", table); - await DbMergeUtility.MergeTable(_sqlConnection, connection, table, _logger); - } - } - - // Re-enable foreign key checks and verify integrity. - await using (var command = _sqlConnection.CreateCommand()) - { - command.CommandText = "PRAGMA foreign_keys = ON;"; - await command.ExecuteNonQueryAsync(); - - // Verify foreign key integrity. command.CommandText = "PRAGMA foreign_key_check;"; await using var reader = await command.ExecuteReaderAsync(); if (await reader.ReadAsync()) @@ -186,13 +201,6 @@ public sealed class DbService : IDisposable // Update the db context with the new merged database. _dbContext = new AliasClientDbContext(_sqlConnection, log => _logger.LogDebug("{Message}", log)); - // Clean up other connections. - foreach (var connection in sqlConnections) - { - await connection.CloseAsync(); - await connection.DisposeAsync(); - } - // Update the current vault revision number to the highest revision number in the merged database(s). // This is important so the server knows that the local client has successfully merged the databases // and should overwrite the existing database on the server with the new merged database. @@ -671,6 +679,25 @@ public sealed class DbService : IDisposable File.Delete(tempFileName); } + /// + /// Converts a JsonElement to its appropriate .NET value for SQLite parameters. + /// + /// The JsonElement to convert. + /// The converted value. + private static object? ConvertJsonElementToValue(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.TryGetInt64(out var longVal) ? longVal : element.GetDouble(), + JsonValueKind.True => 1L, // SQLite stores booleans as integers + JsonValueKind.False => 0L, + JsonValueKind.Null => null, + JsonValueKind.Undefined => null, + _ => element.ToString(), + }; + } + /// /// Checks if there are any pending migrations. /// @@ -894,6 +921,136 @@ public sealed class DbService : IDisposable } } + /// + /// Reads all specified tables from a SQLite connection as JSON data for the Rust merge. + /// + /// The SQLite connection to read from. + /// The names of tables to read. + /// List of TableData objects containing the table records. + private async Task> ReadTablesAsJsonAsync(SqliteConnection connection, string[] tableNames) + { + var tables = new List(); + + foreach (var tableName in tableNames) + { + var tableData = new TableData { Name = tableName }; + + // Check if table exists in the database. + await using var checkCommand = connection.CreateCommand(); + checkCommand.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name=@tableName"; + checkCommand.Parameters.AddWithValue("@tableName", tableName); + var exists = await checkCommand.ExecuteScalarAsync(); + + if (exists == null) + { + // Table doesn't exist, add empty table data. + tables.Add(tableData); + continue; + } + + // Get column names for the table. + await using var columnsCommand = connection.CreateCommand(); + columnsCommand.CommandText = $"PRAGMA table_info({tableName})"; + var columns = new List(); + await using (var columnsReader = await columnsCommand.ExecuteReaderAsync()) + { + while (await columnsReader.ReadAsync()) + { + columns.Add(columnsReader.GetString(1)); + } + } + + // Read all records from the table. + await using var selectCommand = connection.CreateCommand(); + selectCommand.CommandText = $"SELECT * FROM {tableName}"; + await using var reader = await selectCommand.ExecuteReaderAsync(); + + while (await reader.ReadAsync()) + { + var record = new Dictionary(); + for (var i = 0; i < columns.Count; i++) + { + var value = reader.GetValue(i); + + // Convert DBNull to null for proper JSON serialization. + record[columns[i]] = value == DBNull.Value ? null : value; + } + + tableData.Records.Add(record); + } + + tables.Add(tableData); + } + + return tables; + } + + /// + /// Executes the SQL statements returned by the Rust merge operation. + /// + /// The SQL statements to execute. + /// Task. + private async Task ExecuteMergeSqlStatementsAsync(List statements) + { + if (statements.Count == 0) + { + _logger.LogDebug("No SQL statements to execute from merge."); + return; + } + + _logger.LogDebug("Executing {Count} SQL statements from merge.", statements.Count); + + // Disable foreign key checks during merge execution. + await using (var pragmaCommand = _sqlConnection!.CreateCommand()) + { + pragmaCommand.CommandText = "PRAGMA foreign_keys = OFF;"; + await pragmaCommand.ExecuteNonQueryAsync(); + } + + try + { + foreach (var statement in statements) + { + await using var command = _sqlConnection!.CreateCommand(); + command.CommandText = statement.Sql; + + // Add parameters in order (SQLite uses positional parameters with ?). + for (var i = 0; i < statement.Params.Count; i++) + { + var value = statement.Params[i]; + + // Handle JsonElement values from deserialization. + if (value is JsonElement jsonElement) + { + value = ConvertJsonElementToValue(jsonElement); + } + + command.Parameters.AddWithValue($"@p{i}", value ?? DBNull.Value); + } + + // Replace ? placeholders with named parameters. + var paramIndex = 0; + var sql = statement.Sql; + while (sql.Contains('?')) + { + var pos = sql.IndexOf('?'); + sql = sql[..pos] + $"@p{paramIndex}" + sql[(pos + 1)..]; + paramIndex++; + } + + command.CommandText = sql; + await command.ExecuteNonQueryAsync(); + } + } + finally + { + // Re-enable foreign key checks. + await using var pragmaCommand = _sqlConnection!.CreateCommand(); + pragmaCommand.CommandText = "PRAGMA foreign_keys = ON;"; + await pragmaCommand.ExecuteNonQueryAsync(); + } + } + /// /// Disposes the service. /// diff --git a/apps/server/AliasVault.Client/Services/Native/MergeInput.cs b/apps/server/AliasVault.Client/Services/Native/MergeInput.cs new file mode 100644 index 000000000..df1d97457 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/MergeInput.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +using System.Text.Json.Serialization; + +/// +/// Input for the vault merge operation. +/// +public class MergeInput +{ + /// + /// Gets or sets tables from the local database. + /// + [JsonPropertyName("local_tables")] + public List LocalTables { get; set; } = []; + + /// + /// Gets or sets tables from the server database. + /// + [JsonPropertyName("server_tables")] + public List ServerTables { get; set; } = []; +} diff --git a/apps/server/AliasVault.Client/Services/Native/MergeOutput.cs b/apps/server/AliasVault.Client/Services/Native/MergeOutput.cs new file mode 100644 index 000000000..55ee305e6 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/MergeOutput.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +using System.Text.Json.Serialization; + +/// +/// Output of the merge operation. +/// +public class MergeOutput +{ + /// + /// Gets or sets a value indicating whether the merge was successful. + /// + [JsonPropertyName("success")] + public bool Success { get; set; } + + /// + /// Gets or sets SQL statements to execute on the local database (in order). + /// + [JsonPropertyName("statements")] + public List Statements { get; set; } = []; + + /// + /// Gets or sets overall statistics. + /// + [JsonPropertyName("stats")] + public MergeStats Stats { get; set; } = new(); + + /// + /// Gets or sets error message if success is false. + /// + [JsonPropertyName("error")] + public string? Error { get; set; } +} diff --git a/apps/server/AliasVault.Client/Services/Native/MergeStats.cs b/apps/server/AliasVault.Client/Services/Native/MergeStats.cs new file mode 100644 index 000000000..be0868778 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/MergeStats.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +using System.Text.Json.Serialization; + +/// +/// Statistics about what was merged. +/// +public class MergeStats +{ + /// + /// Gets or sets the number of tables processed. + /// + [JsonPropertyName("tables_processed")] + public int TablesProcessed { get; set; } + + /// + /// Gets or sets records where local version was kept. + /// + [JsonPropertyName("records_from_local")] + public int RecordsFromLocal { get; set; } + + /// + /// Gets or sets records where server version was used (updates). + /// + [JsonPropertyName("records_from_server")] + public int RecordsFromServer { get; set; } + + /// + /// Gets or sets records that only existed locally (created offline). + /// + [JsonPropertyName("records_created_locally")] + public int RecordsCreatedLocally { get; set; } + + /// + /// Gets or sets number of conflicts resolved (both had the record). + /// + [JsonPropertyName("conflicts")] + public int Conflicts { get; set; } + + /// + /// Gets or sets records inserted from server (server-only records). + /// + [JsonPropertyName("records_inserted")] + public int RecordsInserted { get; set; } +} diff --git a/apps/server/AliasVault.Client/Services/Native/RustCore.cs b/apps/server/AliasVault.Client/Services/Native/RustCore.cs new file mode 100644 index 000000000..81c39af41 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/RustCore.cs @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +using System.Text.Json; +using Microsoft.JSInterop; + +/// +/// JavaScript interop wrapper for the Rust WASM core library. +/// Provides vault merge and credential matching functionality via WASM. +/// +public class RustCore : IAsyncDisposable +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + WriteIndented = false, + }; + + private readonly IJSRuntime jsRuntime; + private bool? isAvailable; + + /// + /// Initializes a new instance of the class. + /// + /// The JS runtime for interop. + public RustCore(IJSRuntime jsRuntime) + { + this.jsRuntime = jsRuntime; + } + + /// + /// Check if the Rust WASM module is available. + /// + /// True if the WASM module is loaded and available. + public async Task IsAvailableAsync() + { + if (isAvailable.HasValue) + { + return isAvailable.Value; + } + + try + { + isAvailable = await jsRuntime.InvokeAsync("rustCoreIsAvailable"); + return isAvailable.Value; + } + catch + { + isAvailable = false; + return false; + } + } + + /// + /// Merge two vaults using Last-Write-Wins (LWW) strategy. + /// + /// The merge input containing local and server tables. + /// The merge output with SQL statements to execute. + /// Thrown if merge fails or WASM module is unavailable. + public async Task MergeVaultsAsync(MergeInput input) + { + if (!await IsAvailableAsync()) + { + throw new InvalidOperationException("Rust WASM module is not available."); + } + + var inputJson = JsonSerializer.Serialize(input, JsonOptions); + var resultJson = await jsRuntime.InvokeAsync("rustCoreMergeVaults", inputJson); + + if (string.IsNullOrEmpty(resultJson)) + { + throw new InvalidOperationException("Merge operation returned empty result."); + } + + var result = JsonSerializer.Deserialize(resultJson, JsonOptions); + if (result == null) + { + throw new InvalidOperationException("Failed to deserialize merge result."); + } + + if (!result.Success && !string.IsNullOrEmpty(result.Error)) + { + throw new InvalidOperationException($"Merge failed: {result.Error}"); + } + + return result; + } + + /// + /// Get the list of table names that need to be synced. + /// + /// Array of table names. + public async Task GetSyncableTableNamesAsync() + { + if (!await IsAvailableAsync()) + { + return SyncableTables.Names; + } + + try + { + var result = await jsRuntime.InvokeAsync("rustCoreGetSyncableTableNames"); + return result ?? SyncableTables.Names; + } + catch + { + return SyncableTables.Names; + } + } + + /// + /// Extract domain from URL. + /// + /// The URL to extract domain from. + /// The extracted domain. + public async Task ExtractDomainAsync(string url) + { + if (!await IsAvailableAsync()) + { + return string.Empty; + } + + try + { + return await jsRuntime.InvokeAsync("rustCoreExtractDomain", url); + } + catch + { + return string.Empty; + } + } + + /// + /// Extract root domain from a domain string. + /// + /// The domain to extract root from. + /// The root domain. + public async Task ExtractRootDomainAsync(string domain) + { + if (!await IsAvailableAsync()) + { + return string.Empty; + } + + try + { + return await jsRuntime.InvokeAsync("rustCoreExtractRootDomain", domain); + } + catch + { + return string.Empty; + } + } + + /// + public ValueTask DisposeAsync() + { + return ValueTask.CompletedTask; + } +} diff --git a/apps/server/AliasVault.Client/Services/Native/SqlStatement.cs b/apps/server/AliasVault.Client/Services/Native/SqlStatement.cs new file mode 100644 index 000000000..38f90301f --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/SqlStatement.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +using System.Text.Json.Serialization; + +/// +/// A SQL statement with its parameter values, generated by the merge operation. +/// +public class SqlStatement +{ + /// + /// Gets or sets the SQL query with ? placeholders. + /// + [JsonPropertyName("sql")] + public string Sql { get; set; } = string.Empty; + + /// + /// Gets or sets parameter values in order. + /// + [JsonPropertyName("params")] + public List Params { get; set; } = []; +} diff --git a/apps/server/AliasVault.Client/Services/Native/SyncableTables.cs b/apps/server/AliasVault.Client/Services/Native/SyncableTables.cs new file mode 100644 index 000000000..768e64b8b --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/SyncableTables.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +/// +/// List of syncable table names that need to be read for merge operations. +/// +public static class SyncableTables +{ + /// + /// Table names that need LWW merge. + /// + public static readonly string[] Names = + [ + "Items", + "FieldValues", + "Folders", + "Tags", + "ItemTags", + "Attachments", + "TotpCodes", + "Passkeys", + "FieldDefinitions", + "FieldHistories", + "Logos", + ]; +} diff --git a/apps/server/AliasVault.Client/Services/Native/TableData.cs b/apps/server/AliasVault.Client/Services/Native/TableData.cs new file mode 100644 index 000000000..ff1519163 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/Native/TableData.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) aliasvault. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.Native; + +using System.Text.Json.Serialization; + +/// +/// Data for a single database table, used in vault merge operations. +/// +public class TableData +{ + /// + /// Gets or sets the table name. + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets all records in this table as dictionaries. + /// + [JsonPropertyName("records")] + public List> Records { get; set; } = []; +} diff --git a/apps/server/AliasVault.Client/wwwroot/js/rustCoreInterop.js b/apps/server/AliasVault.Client/wwwroot/js/rustCoreInterop.js new file mode 100644 index 000000000..0a08547b8 --- /dev/null +++ b/apps/server/AliasVault.Client/wwwroot/js/rustCoreInterop.js @@ -0,0 +1,175 @@ +// Rust Core WASM Interop for Blazor +// This module provides JavaScript functions that Blazor can call via JSInterop +// to access the Rust WASM merge and credential matching functionality. + +let wasmModule = null; +let isInitialized = false; +let initPromise = null; + +/** + * Initialize the Rust WASM module. + * @returns {Promise} True if initialization succeeded. + */ +async function initRustCore() { + if (isInitialized) { + return true; + } + + if (initPromise) { + return initPromise; + } + + initPromise = (async () => { + try { + // Fetch the WASM binary first + const wasmResponse = await fetch('/wasm/aliasvault_core_bg.wasm'); + if (!wasmResponse.ok) { + throw new Error(`Failed to fetch WASM: ${wasmResponse.status}`); + } + const wasmBytes = await wasmResponse.arrayBuffer(); + + // Dynamically import the ES module + const module = await import('/wasm/aliasvault_core.js'); + + // Initialize the WASM module with the binary bytes + await module.default(wasmBytes); + + // Call init to set up panic hook + if (typeof module.init === 'function') { + module.init(); + } + + wasmModule = module; + isInitialized = true; + console.log('[RustCore] WASM module initialized successfully'); + return true; + } catch (error) { + console.error('[RustCore] Failed to initialize WASM module:', error); + isInitialized = false; + initPromise = null; // Allow retry on failure + return false; + } + })(); + + return initPromise; +} + +/** + * Check if the Rust WASM module is available. + * @returns {Promise} True if available. + */ +window.rustCoreIsAvailable = async function() { + return await initRustCore(); +}; + +/** + * Merge two vaults using LWW strategy. + * @param {string} inputJson - JSON string containing MergeInput. + * @returns {Promise} JSON string containing MergeOutput. + */ +window.rustCoreMergeVaults = async function(inputJson) { + if (!await initRustCore()) { + return JSON.stringify({ + success: false, + error: 'Rust WASM module not available', + statements: [], + stats: {} + }); + } + + try { + const result = wasmModule.mergeVaultsJson(inputJson); + return result; + } catch (error) { + console.error('[RustCore] Merge failed:', error); + return JSON.stringify({ + success: false, + error: error.toString(), + statements: [], + stats: {} + }); + } +}; + +/** + * Filter credentials for autofill. + * @param {string} inputJson - JSON string containing CredentialMatcherInput. + * @returns {Promise} JSON string containing CredentialMatcherOutput. + */ +window.rustCoreFilterCredentials = async function(inputJson) { + if (!await initRustCore()) { + return JSON.stringify({ + matches: [], + error: 'Rust WASM module not available' + }); + } + + try { + const result = wasmModule.filterCredentialsJson(inputJson); + return result; + } catch (error) { + console.error('[RustCore] Filter credentials failed:', error); + return JSON.stringify({ + matches: [], + error: error.toString() + }); + } +}; + +/** + * Get the list of syncable table names. + * @returns {Promise} Array of table names. + */ +window.rustCoreGetSyncableTableNames = async function() { + if (!await initRustCore()) { + // Return default list if WASM not available + return [ + 'Items', 'FieldValues', 'Folders', 'Tags', 'ItemTags', + 'Attachments', 'TotpCodes', 'Passkeys', 'FieldDefinitions', + 'FieldHistories', 'Logos' + ]; + } + + try { + return wasmModule.getSyncableTableNames(); + } catch (error) { + console.error('[RustCore] Get syncable table names failed:', error); + return []; + } +}; + +/** + * Extract domain from URL. + * @param {string} url - The URL to extract domain from. + * @returns {Promise} The extracted domain. + */ +window.rustCoreExtractDomain = async function(url) { + if (!await initRustCore()) { + return ''; + } + + try { + return wasmModule.extractDomain(url); + } catch (error) { + console.error('[RustCore] Extract domain failed:', error); + return ''; + } +}; + +/** + * Extract root domain from a domain string. + * @param {string} domain - The domain to extract root from. + * @returns {Promise} The root domain. + */ +window.rustCoreExtractRootDomain = async function(domain) { + if (!await initRustCore()) { + return ''; + } + + try { + return wasmModule.extractRootDomain(domain); + } catch (error) { + console.error('[RustCore] Extract root domain failed:', error); + return ''; + } +}; diff --git a/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core.js b/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core.js new file mode 100644 index 000000000..8b76896bc --- /dev/null +++ b/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core.js @@ -0,0 +1,743 @@ +let wasm; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function getArrayJsValueFromWasm0(ptr, len) { + ptr = ptr >>> 0; + const mem = getDataViewMemory0(); + const result = []; + for (let i = ptr; i < ptr + 4 * len; i += 4) { + result.push(takeObject(mem.getUint32(i, true))); + } + return result; +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +let cachedDataViewMemory0 = null; +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return decodeText(ptr, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getObject(idx) { return heap[idx]; } + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_export3(addHeapObject(e)); + } +} + +let heap = new Array(128).fill(undefined); +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + } +} + +let WASM_VECTOR_LEN = 0; + +/** + * Extract domain from URL. + * + * Handles both full URLs and partial domains, returning normalized domain + * without protocol, www prefix, path, query, or fragment. + * @param {string} url + * @returns {string} + */ +export function extractDomain(url) { + let deferred2_0; + let deferred2_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(url, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + wasm.extractDomain(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred2_0 = r0; + deferred2_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1); + } +} + +/** + * Extract root domain from a domain string. + * + * E.g., "sub.example.com" -> "example.com" + * E.g., "sub.example.co.uk" -> "example.co.uk" + * @param {string} domain + * @returns {string} + */ +export function extractRootDomain(domain) { + let deferred2_0; + let deferred2_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(domain, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + wasm.extractRootDomain(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred2_0 = r0; + deferred2_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1); + } +} + +/** + * Filter credentials for autofill. + * + * Takes a JsValue (CredentialMatcherInput) and returns a JsValue (CredentialMatcherOutput). + * @param {any} input + * @returns {any} + */ +export function filterCredentials(input) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.filterCredentials(retptr, addHeapObject(input)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** + * Filter credentials using JSON strings (alternative API). + * + * Takes a JSON string and returns a JSON string. + * @param {string} input_json + * @returns {string} + */ +export function filterCredentialsJson(input_json) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(input_json, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + wasm.filterCredentialsJson(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1); + } +} + +/** + * Get the list of table names that need to be synced. + * @returns {string[]} + */ +export function getSyncableTableNames() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.getSyncableTableNames(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayJsValueFromWasm0(r0, r1).slice(); + wasm.__wbindgen_export4(r0, r1 * 4, 4); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** + * Initialize panic hook for better error messages. + */ +export function init() { + wasm.init(); +} + +/** + * Merge vaults using LWW strategy. + * + * Takes a JsValue (MergeInput) and returns a JsValue (MergeOutput). + * @param {any} input + * @returns {any} + */ +export function mergeVaults(input) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.mergeVaults(retptr, addHeapObject(input)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** + * Merge vaults using JSON strings (alternative API). + * + * Takes a JSON string and returns a JSON string. + * @param {string} input_json + * @returns {string} + */ +export function mergeVaultsJson(input_json) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(input_json, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + wasm.mergeVaultsJson(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1); + } +} + +const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']); + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = module.ok && EXPECTED_RESPONSE_TYPES.has(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_Error_52673b7de5a0ca89 = function(arg0, arg1) { + const ret = Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_String_8f0eb39a4a4c2f66 = function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg___wbindgen_bigint_get_as_i64_6e32f5e6aff02e1d = function(arg0, arg1) { + const v = getObject(arg1); + const ret = typeof(v) === 'bigint' ? v : undefined; + getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbg___wbindgen_boolean_get_dea25b33882b895b = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? v : undefined; + return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0; + }; + imports.wbg.__wbg___wbindgen_debug_string_adfb662ae34724b6 = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg___wbindgen_in_0d3e1e8f0c669317 = function(arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbg___wbindgen_is_bigint_0e1a2e3f55cfae27 = function(arg0) { + const ret = typeof(getObject(arg0)) === 'bigint'; + return ret; + }; + imports.wbg.__wbg___wbindgen_is_function_8d400b8b1af978cd = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbg___wbindgen_is_object_ce774f3490692386 = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbg___wbindgen_is_string_704ef9c8fc131030 = function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }; + imports.wbg.__wbg___wbindgen_is_undefined_f6b95eab589e0269 = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbg___wbindgen_jsval_eq_b6101cc9cef1fe36 = function(arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1); + return ret; + }; + imports.wbg.__wbg___wbindgen_jsval_loose_eq_766057600fdd1b0d = function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }; + imports.wbg.__wbg___wbindgen_number_get_9619185a74197f95 = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbg___wbindgen_string_get_a2a31e16edf96e42 = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg___wbindgen_throw_dd24417ed36fc46e = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbg_call_abb4ff46ce38be40 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_done_62ea16af4ce34b24 = function(arg0) { + const ret = getObject(arg0).done; + return ret; + }; + imports.wbg.__wbg_entries_83c79938054e065f = function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_export4(deferred0_0, deferred0_1, 1); + } + }; + imports.wbg.__wbg_get_6b7bd52aca3f9671 = function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_af9dab7e9603ea93 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_get_with_ref_key_1dc361bd10053bfe = function(arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_ArrayBuffer_f3320d2419cd0355 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Map_084be8da74364158 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Map; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_da54ccc9d3e09434 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isArray_51fd9e6422c0a395 = function(arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_isSafeInteger_ae7d3f054d55fa16 = function(arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_iterator_27b7c8b35ab3e86b = function() { + const ret = Symbol.iterator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_22ac23eaec9d8053 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_d45040a40c570362 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_1ba21ce319a06297 = function() { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_25f239778d6112b9 = function() { + const ret = new Array(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_6421f6084cc5bc5a = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_b546ae120718850e = function() { + const ret = new Map(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_138a17bbf04e926c = function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_3cfe5c0fe2a4cc53 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_prototypesetcall_dfe9b766cdc1f1fd = function(arg0, arg1, arg2) { + Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2)); + }; + imports.wbg.__wbg_set_3f1d0b984ed272ed = function(arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }; + imports.wbg.__wbg_set_7df433eea03a5c14 = function(arg0, arg1, arg2) { + getObject(arg0)[arg1 >>> 0] = takeObject(arg2); + }; + imports.wbg.__wbg_set_efaaf145b9377369 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).set(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_value_57b7b035e117f7ee = function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cast_4625c577ab2ec9ee = function(arg0) { + // Cast intrinsic for `U64 -> Externref`. + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cast_9ae0607507abb057 = function(arg0) { + // Cast intrinsic for `I64 -> Externref`. + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cast_d6cd19b81560fd6e = function(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + + return imports; +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('aliasvault_core_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm b/apps/server/AliasVault.Client/wwwroot/wasm/aliasvault_core_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d86832e8669bb5f14d1da9d00a713a3eb967f77a GIT binary patch literal 291482 zcmeFa3!GioRo{Oe_c{08nWL92>*aH=6_CJkW~6yYAtZAqj%{Hc4l${lfBzqOq!~FL zDY7L`9OqwI8WSZ5Fn?U?f++4kf;iYvJ8{4SQ^G$oxZo0#7%*Vkv@yW}K^!o|0YhEf z?{DpM&OP_e=wVX_`k0aC?6Y5M?X}lld#$w}$D6+8-M;5}{^P;xulKy`J^$$S{!x$r z7VwW;9~|YCKfLSjk?X^wiqT)9>~ZA!5^qG>gIM=Ga(!9R#JeX66^kGy4G>jzQN(2- zBI_>l$dMziQXZ<~2)TghBK1P#krc49@_KuU++lk=theCk5vmN+(no}LDJyow)dC=^ z)Jg|ODO@^gW$i%$UsVLk%pcVTSZGnCrt~_l}QyTY= z58X6>)BO8xo`p7hruXmPHM4hi$L@*o@jWwpM)r>EEz;+PT{9xd_}1C!k@4xhqkCq- zZtJM4FU9-T>HY7S-8D9`dt&$Swz1ifk%_$%v$HPC>s;$^JV5&o?3ul3@2+o~zIhkO z@1EU0Gd(f7ZG3cOY`e>Q>7u;fb>o5g?a84-)9>3gGd)IwY0kFU;T@yfQVg~%Dq^{( z-m`b_?4ezIw{6=xJhOFlWXGNvFdK7VjL&Y^r5@WoHab2%JF<0nVr2XFvEfAfSPW4m{3o0!-(zQd~*`do}SPNQeWp!W2(y*tJyMtXQpAhwRd6FVj*w$F@@ zjBMS#V-bjnnZ2Xiw$08?4{w_u+cPrim@{R)v^!@;$A(9Tr$>kPj7cdU$;6jtPf#3UKxS17h};UE8`C5ANDMJ~BK4C-0eMtj%s4ch#q3>8&OM#ds(eb0<5bJA9vU43ZJ+pWB`1sao zWc9?>(cS4x6&HxNXZjX8XPRLBl&lXrXp@pw$bse zV~8i}+RChh{9n4Z8D^Q09lN)WjEzGLhRc@lGxaCxjlo)29*oNTHZE%n4i1(pQBEDD!CH;C#s&3q zZDlPgm&?l=jlpuQw!E^uLK+c+joOOk%LiAKLw-u-$^|P{G-~BarFIdC<*-tzG@=VD zVNkBr>$OI+Rxeiu8x@jP*Oj48Eo82T7gK9FszjB+ib96PV5L@zR}GSl0%VJ7l?#Zf z(H@mmLE1-V`w59u!AecZmIGLdTGecp%bP2M^+qkE`f_L$4mK2O=#3w8(sVVcUaMWq z8>wZgte1y2Txdls+h8NOaBy{Puo46t)QWO>C(1n3Qo znqbw+7pQ5MC@~(aRDx=Qb_L}i3>#qxb!ydeIcPL00a$$F24z#r>NQAIYw$xAHC0b% zmTUHO?G(N1l}3F-;Ri_TI<%K(xhW>7K|Oxxk4jTN#0`QK98j)N!D^aHZ9x#wVU
BpcR)XRe#g2o0YBN8+M)zPSl`uYjV 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 +// This file is auto-generated from core/models/src/vault/SystemFieldRegistry.ts +// Do not edit this file directly. Run 'npm run generate:models' to regenerate. + +#nullable enable + +namespace AliasClientDb.Models; + +/// +/// Field categories for grouping in UI. +/// +public enum FieldCategory +{ + /// Primary fields. + Primary, + /// Login fields. + Login, + /// Alias fields. + Alias, + /// Card fields. + Card, + /// Custom fields. + Custom, + /// Notes fields. + Notes, + /// Metadata fields. + Metadata +} + +/// +/// Per-item-type configuration for a system field. +/// +/// Whether this field is shown by default in create mode (vs. hidden behind an "add" button) +public record ItemTypeFieldConfig(bool ShowByDefault); + +/// +/// System field definition with metadata. +/// +/// Unique system field key (e.g., 'login.username') +/// Field type for rendering/validation +/// Whether field is hidden/masked by default +/// Whether field supports multiple values +/// ApplicableToTypes +/// Whether to track field value history +/// Category for grouping in UI. 'Primary' fields are shown in the name block. +/// Default display order within category (lower = first) +public record SystemFieldDefinition( + string FieldKey, + string FieldType, + bool IsHidden, + bool IsMultiValue, + IReadOnlyDictionary ApplicableToTypes, + bool EnableHistory, + FieldCategory Category, + int DefaultDisplayOrder); + +/// +/// Registry of all system-defined fields. +/// These fields are immutable and their metadata is defined in code. +/// +public static class SystemFieldRegistry +{ + /// + /// All system field definitions indexed by field key. + /// + public static readonly IReadOnlyDictionary Fields = + new Dictionary + { + [FieldKey.LoginUsername] = new SystemFieldDefinition( + FieldKey: "login.username", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Login"] = new ItemTypeFieldConfig(true), ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: true, + Category: FieldCategory.Login, + DefaultDisplayOrder: 10), + [FieldKey.LoginPassword] = new SystemFieldDefinition( + FieldKey: "login.password", + FieldType: "Password", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Login"] = new ItemTypeFieldConfig(true), ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: true, + Category: FieldCategory.Login, + DefaultDisplayOrder: 20), + [FieldKey.LoginEmail] = new SystemFieldDefinition( + FieldKey: "login.email", + FieldType: "Email", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Login"] = new ItemTypeFieldConfig(false), ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: true, + Category: FieldCategory.Login, + DefaultDisplayOrder: 15), + [FieldKey.LoginUrl] = new SystemFieldDefinition( + FieldKey: "login.url", + FieldType: "URL", + IsHidden: false, + IsMultiValue: true, + ApplicableToTypes: new Dictionary { ["Login"] = new ItemTypeFieldConfig(true), ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Primary, + DefaultDisplayOrder: 5), + [FieldKey.AliasFirstName] = new SystemFieldDefinition( + FieldKey: "alias.first_name", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Alias, + DefaultDisplayOrder: 20), + [FieldKey.AliasLastName] = new SystemFieldDefinition( + FieldKey: "alias.last_name", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Alias, + DefaultDisplayOrder: 30), + [FieldKey.AliasGender] = new SystemFieldDefinition( + FieldKey: "alias.gender", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Alias, + DefaultDisplayOrder: 50), + [FieldKey.AliasBirthdate] = new SystemFieldDefinition( + FieldKey: "alias.birthdate", + FieldType: "Date", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Alias"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Alias, + DefaultDisplayOrder: 60), + [FieldKey.CardCardholderName] = new SystemFieldDefinition( + FieldKey: "card.cardholder_name", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["CreditCard"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Card, + DefaultDisplayOrder: 10), + [FieldKey.CardNumber] = new SystemFieldDefinition( + FieldKey: "card.number", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["CreditCard"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Card, + DefaultDisplayOrder: 20), + [FieldKey.CardExpiryMonth] = new SystemFieldDefinition( + FieldKey: "card.expiry_month", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["CreditCard"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Card, + DefaultDisplayOrder: 30), + [FieldKey.CardExpiryYear] = new SystemFieldDefinition( + FieldKey: "card.expiry_year", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["CreditCard"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Card, + DefaultDisplayOrder: 40), + [FieldKey.CardCvv] = new SystemFieldDefinition( + FieldKey: "card.cvv", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["CreditCard"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Card, + DefaultDisplayOrder: 50), + [FieldKey.CardPin] = new SystemFieldDefinition( + FieldKey: "card.pin", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["CreditCard"] = new ItemTypeFieldConfig(false) }, + EnableHistory: false, + Category: FieldCategory.Card, + DefaultDisplayOrder: 60), + [FieldKey.NotesContent] = new SystemFieldDefinition( + FieldKey: "notes.content", + FieldType: "TextArea", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: new Dictionary { ["Login"] = new ItemTypeFieldConfig(false), ["Alias"] = new ItemTypeFieldConfig(false), ["CreditCard"] = new ItemTypeFieldConfig(false), ["Note"] = new ItemTypeFieldConfig(true) }, + EnableHistory: false, + Category: FieldCategory.Notes, + DefaultDisplayOrder: 100) + }; + + /// + /// Get system field definition by key. + /// + /// The field key to look up. + /// The field definition, or null if not found. + public static SystemFieldDefinition? GetSystemField(string fieldKey) + { + return Fields.TryGetValue(fieldKey, out var field) ? field : null; + } + + /// + /// Check if a field key represents a system field. + /// + /// The field key to check. + /// True if the field key is a system field. + public static bool IsSystemField(string fieldKey) + { + return Fields.ContainsKey(fieldKey); + } + + /// + /// Check if a field applies to a specific item type. + /// + /// The field definition. + /// The item type to check. + /// True if the field applies to the item type. + public static bool FieldAppliesToType(SystemFieldDefinition field, string itemType) + { + return field.ApplicableToTypes.ContainsKey(itemType); + } + + /// + /// Get all system fields applicable to a specific item type. + /// Results are sorted by DefaultDisplayOrder. + /// + /// The item type. + /// Fields applicable to the item type. + public static IEnumerable GetFieldsForItemType(string itemType) + { + return Fields.Values + .Where(f => f.ApplicableToTypes.ContainsKey(itemType)) + .OrderBy(f => f.DefaultDisplayOrder); + } + + /// + /// Get system fields that should be shown by default for a specific item type. + /// Results are sorted by DefaultDisplayOrder. + /// + /// The item type. + /// Fields shown by default for the item type. + public static IEnumerable GetDefaultFieldsForItemType(string itemType) + { + return Fields.Values + .Where(f => f.ApplicableToTypes.TryGetValue(itemType, out var config) && config.ShowByDefault) + .OrderBy(f => f.DefaultDisplayOrder); + } + + /// + /// Get system fields that are NOT shown by default for a specific item type. + /// These are the fields that can be added via an "add field" button. + /// Results are sorted by DefaultDisplayOrder. + /// + /// The item type. + /// Optional fields for the item type. + public static IEnumerable GetOptionalFieldsForItemType(string itemType) + { + return Fields.Values + .Where(f => f.ApplicableToTypes.TryGetValue(itemType, out var config) && !config.ShowByDefault) + .OrderBy(f => f.DefaultDisplayOrder); + } + + /// + /// Check if a field key matches a known system field prefix. + /// + /// The field key to check. + /// True if the field key has a system field prefix. + public static bool IsSystemFieldPrefix(string fieldKey) + { + return fieldKey.StartsWith("login.") || + fieldKey.StartsWith("alias.") || + fieldKey.StartsWith("card.") || + fieldKey.StartsWith("notes."); + } +} diff --git a/core/models/scripts/generate-field-keys.cjs b/core/models/scripts/generate-field-keys.cjs index 2039e0306..09ea1c65a 100755 --- a/core/models/scripts/generate-field-keys.cjs +++ b/core/models/scripts/generate-field-keys.cjs @@ -1,6 +1,7 @@ #!/usr/bin/env node /** - * Generates FieldKey constants for C#, Swift, and Kotlin from TypeScript source. + * Generates FieldKey constants and SystemFieldRegistry for C#, Swift, and Kotlin from TypeScript source. + * All type definitions are dynamically extracted from the TypeScript source files. */ const fs = require('fs'); @@ -9,7 +10,9 @@ const path = require('path'); // Paths const REPO_ROOT = path.join(__dirname, '../../..'); const TS_SOURCE = path.join(REPO_ROOT, 'core/models/src/vault/FieldKey.ts'); +const TS_REGISTRY_SOURCE = path.join(REPO_ROOT, 'core/models/src/vault/SystemFieldRegistry.ts'); const CS_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/FieldKey.cs'); +const CS_REGISTRY_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/SystemFieldRegistry.cs'); const SWIFT_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/ios/VaultModels/FieldKey.swift'); const KOTLIN_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/android/app/src/main/java/net/aliasvault/app/vaultstore/models/FieldKey.kt'); @@ -50,7 +53,140 @@ function parseTypeScriptFieldKeys(tsContent) { } /** - * Generate C# static class + * Parse FieldCategories from TypeScript source + * Returns an array of category names in order + */ +function parseFieldCategories(tsContent) { + const categories = []; + + // Find the FieldCategories constant + const match = tsContent.match(/export const FieldCategories\s*=\s*\{([^}]+)\}/s); + if (!match) { + console.warn('Warning: Could not find FieldCategories in source'); + return categories; + } + + const body = match[1]; + // Match each category: Primary: 'Primary', + const categoryRegex = /(\w+):\s*'(\w+)'/g; + let categoryMatch; + + while ((categoryMatch = categoryRegex.exec(body)) !== null) { + categories.push(categoryMatch[1]); + } + + return categories; +} + +/** + * Parse ItemTypeFieldConfig type from TypeScript source + * Returns an array of property definitions + */ +function parseItemTypeFieldConfig(tsContent) { + const properties = []; + + // Find the ItemTypeFieldConfig type + const match = tsContent.match(/export type ItemTypeFieldConfig\s*=\s*\{([^}]+)\}/s); + if (!match) { + console.warn('Warning: Could not find ItemTypeFieldConfig in source'); + return properties; + } + + const body = match[1]; + // Match properties with comments + const lines = body.split('\n'); + let currentComment = ''; + + for (const line of lines) { + const trimmed = line.trim(); + + // Capture comments + const commentMatch = trimmed.match(/\/\*\*\s*(.+)\s*\*\//); + if (commentMatch) { + currentComment = commentMatch[1].trim(); + } + + // Match property: PropertyName: type; + const propMatch = trimmed.match(/^(\w+):\s*(\w+);?$/); + if (propMatch) { + properties.push({ + name: propMatch[1], + type: propMatch[2], + comment: currentComment + }); + currentComment = ''; + } + } + + return properties; +} + +/** + * Parse SystemFieldDefinition type from TypeScript source + * Returns an array of property definitions + */ +function parseSystemFieldDefinition(tsContent) { + const properties = []; + + // Find the SystemFieldDefinition type + const match = tsContent.match(/export type SystemFieldDefinition\s*=\s*\{([\s\S]*?)\n\};/); + if (!match) { + console.warn('Warning: Could not find SystemFieldDefinition in source'); + return properties; + } + + const body = match[1]; + const lines = body.split('\n'); + let currentComment = ''; + + for (const line of lines) { + const trimmed = line.trim(); + + // Capture single-line comments + const singleCommentMatch = trimmed.match(/\/\*\*\s*(.+)\s*\*\//); + if (singleCommentMatch) { + currentComment = singleCommentMatch[1].trim(); + continue; + } + + // Match property definition + const propMatch = trimmed.match(/^(\w+):\s*(.+);?$/); + if (propMatch && !trimmed.startsWith('/*') && !trimmed.startsWith('*')) { + let propType = propMatch[2].replace(/;$/, '').trim(); + + // Simplify complex types for C# mapping + let csharpType = mapTypeToCSharp(propType); + + properties.push({ + name: propMatch[1], + tsType: propType, + csharpType: csharpType, + comment: currentComment + }); + currentComment = ''; + } + } + + return properties; +} + +/** + * Map TypeScript type to C# type + */ +function mapTypeToCSharp(tsType) { + if (tsType === 'string') return 'string'; + if (tsType === 'boolean') return 'bool'; + if (tsType === 'number') return 'int'; + if (tsType === 'FieldType') return 'string'; // FieldType is a string union + if (tsType === 'FieldCategory') return 'FieldCategory'; + if (tsType.includes('Partial>')) { + return 'IReadOnlyDictionary'; + } + return 'string'; // Default fallback +} + +/** + * Generate C# static class for FieldKey */ function generateCSharp(fieldKeys) { const header = `// @@ -148,6 +284,322 @@ object FieldKey {`; return header + '\n' + fields + footer; } +/** + * Parse the TypeScript SystemFieldRegistry.ts file and extract field definitions + */ +function parseSystemFieldRegistry(tsContent) { + const fields = {}; + + // Find the start of SystemFieldRegistry definition + const registryStart = tsContent.indexOf('export const SystemFieldRegistry'); + if (registryStart === -1) { + return fields; + } + + // Extract just the registry content + const registryContent = tsContent.slice(registryStart); + + // Match each field definition block using a state machine approach + // Look for patterns like: 'login.username': { + const fieldStartRegex = /'([a-z]+\.[a-z_]+)':\s*\{/g; + let match; + + while ((match = fieldStartRegex.exec(registryContent)) !== null) { + const fieldKey = match[1]; + const startIdx = match.index + match[0].length; + + // Find matching closing brace by counting braces + let braceCount = 1; + let endIdx = startIdx; + while (braceCount > 0 && endIdx < registryContent.length) { + if (registryContent[endIdx] === '{') braceCount++; + if (registryContent[endIdx] === '}') braceCount--; + endIdx++; + } + + const fieldBody = registryContent.slice(startIdx, endIdx - 1); + + // Skip if this doesn't look like a SystemFieldDefinition (needs FieldKey property) + if (!fieldBody.includes('FieldKey:')) { + continue; + } + + const field = { + FieldKey: fieldKey, + FieldType: extractStringValue(fieldBody, 'FieldType'), + IsHidden: extractBoolValue(fieldBody, 'IsHidden'), + IsMultiValue: extractBoolValue(fieldBody, 'IsMultiValue'), + EnableHistory: extractBoolValue(fieldBody, 'EnableHistory'), + Category: extractEnumValue(fieldBody, 'Category', 'FieldCategories'), + DefaultDisplayOrder: extractNumberValue(fieldBody, 'DefaultDisplayOrder'), + ApplicableToTypes: extractApplicableToTypes(fieldBody) + }; + + fields[fieldKey] = field; + } + + return fields; +} + +/** + * Extract a string value from a field body + */ +function extractStringValue(body, propName) { + const match = body.match(new RegExp(`${propName}:\\s*'([^']+)'`)); + return match ? match[1] : ''; +} + +/** + * Extract a boolean value from a field body + */ +function extractBoolValue(body, propName) { + const match = body.match(new RegExp(`${propName}:\\s*(true|false)`)); + return match ? match[1] === 'true' : false; +} + +/** + * Extract a number value from a field body + */ +function extractNumberValue(body, propName) { + const match = body.match(new RegExp(`${propName}:\\s*(\\d+)`)); + return match ? parseInt(match[1], 10) : 0; +} + +/** + * Extract an enum value from a field body (e.g., FieldCategories.Login -> Login) + */ +function extractEnumValue(body, propName, enumName) { + const match = body.match(new RegExp(`${propName}:\\s*${enumName}\\.(\\w+)`)); + return match ? match[1] : ''; +} + +/** + * Extract ApplicableToTypes object from field body + */ +function extractApplicableToTypes(body) { + const applicableToTypes = {}; + + // Find the start of ApplicableToTypes + const startMatch = body.match(/ApplicableToTypes:\s*\{/); + if (!startMatch) { + return applicableToTypes; + } + + const startIdx = startMatch.index + startMatch[0].length; + + // Find matching closing brace by counting braces + let braceCount = 1; + let endIdx = startIdx; + while (braceCount > 0 && endIdx < body.length) { + if (body[endIdx] === '{') braceCount++; + if (body[endIdx] === '}') braceCount--; + endIdx++; + } + + const typesBody = body.slice(startIdx, endIdx - 1); + + // Match each item type configuration: ItemType: { ShowByDefault: bool } + const itemTypeRegex = /(\w+):\s*\{\s*ShowByDefault:\s*(true|false)\s*\}/g; + let typeMatch; + + while ((typeMatch = itemTypeRegex.exec(typesBody)) !== null) { + const itemType = typeMatch[1]; + const showByDefault = typeMatch[2] === 'true'; + applicableToTypes[itemType] = { ShowByDefault: showByDefault }; + } + + return applicableToTypes; +} + +/** + * Generate C# SystemFieldRegistry with full metadata + * All types are dynamically generated from the parsed TypeScript + */ +function generateCSharpSystemFieldRegistry(fields, categories, itemTypeFieldConfigProps, systemFieldDefProps) { + // Generate FieldCategory enum dynamically + const categoryEnum = categories.map((cat, index) => { + return ` /// ${cat} fields. + ${cat}${index < categories.length - 1 ? ',' : ''}`; + }).join('\n'); + + // Generate ItemTypeFieldConfig record dynamically + const itemTypeFieldConfigParams = itemTypeFieldConfigProps + .map(p => `${mapTypeToCSharp(p.type)} ${p.name}`) + .join(', '); + + // Generate SystemFieldDefinition record dynamically + const systemFieldDefParams = systemFieldDefProps + .map(p => `${p.csharpType} ${p.name}`) + .join(',\n '); + + const systemFieldDefXmlParams = systemFieldDefProps + .map(p => `/// ${p.comment || p.name}`) + .join('\n'); + + const header = `// +// This file is auto-generated from core/models/src/vault/SystemFieldRegistry.ts +// Do not edit this file directly. Run 'npm run generate:models' to regenerate. + +#nullable enable + +namespace AliasClientDb.Models; + +/// +/// Field categories for grouping in UI. +/// +public enum FieldCategory +{ +${categoryEnum} +} + +/// +/// Per-item-type configuration for a system field. +/// +${itemTypeFieldConfigProps.map(p => `/// ${p.comment || p.name}`).join('\n')} +public record ItemTypeFieldConfig(${itemTypeFieldConfigParams}); + +/// +/// System field definition with metadata. +/// +${systemFieldDefXmlParams} +public record SystemFieldDefinition( + ${systemFieldDefParams}); + +/// +/// Registry of all system-defined fields. +/// These fields are immutable and their metadata is defined in code. +/// +public static class SystemFieldRegistry +{ + /// + /// All system field definitions indexed by field key. + /// + public static readonly IReadOnlyDictionary Fields = + new Dictionary + { +`; + + const fieldEntries = Object.entries(fields) + .map(([key, field]) => { + const applicableTypes = Object.entries(field.ApplicableToTypes) + .map(([type, config]) => `["${type}"] = new ItemTypeFieldConfig(${config.ShowByDefault})`) + .join(', '); + + return ` [FieldKey.${fieldKeyToPropertyName(key)}] = new SystemFieldDefinition( + FieldKey: "${field.FieldKey}", + FieldType: "${field.FieldType}", + IsHidden: ${field.IsHidden.toString().toLowerCase()}, + IsMultiValue: ${field.IsMultiValue.toString().toLowerCase()}, + ApplicableToTypes: new Dictionary { ${applicableTypes} }, + EnableHistory: ${field.EnableHistory.toString().toLowerCase()}, + Category: FieldCategory.${field.Category}, + DefaultDisplayOrder: ${field.DefaultDisplayOrder})`; + }) + .join(',\n'); + + // Extract unique prefixes from field keys for IsSystemFieldPrefix + const prefixes = [...new Set(Object.keys(fields).map(k => k.split('.')[0]))]; + const prefixChecks = prefixes.map(p => `fieldKey.StartsWith("${p}.")`).join(' ||\n '); + + const methods = ` + }; + + /// + /// Get system field definition by key. + /// + /// The field key to look up. + /// The field definition, or null if not found. + public static SystemFieldDefinition? GetSystemField(string fieldKey) + { + return Fields.TryGetValue(fieldKey, out var field) ? field : null; + } + + /// + /// Check if a field key represents a system field. + /// + /// The field key to check. + /// True if the field key is a system field. + public static bool IsSystemField(string fieldKey) + { + return Fields.ContainsKey(fieldKey); + } + + /// + /// Check if a field applies to a specific item type. + /// + /// The field definition. + /// The item type to check. + /// True if the field applies to the item type. + public static bool FieldAppliesToType(SystemFieldDefinition field, string itemType) + { + return field.ApplicableToTypes.ContainsKey(itemType); + } + + /// + /// Get all system fields applicable to a specific item type. + /// Results are sorted by DefaultDisplayOrder. + /// + /// The item type. + /// Fields applicable to the item type. + public static IEnumerable GetFieldsForItemType(string itemType) + { + return Fields.Values + .Where(f => f.ApplicableToTypes.ContainsKey(itemType)) + .OrderBy(f => f.DefaultDisplayOrder); + } + + /// + /// Get system fields that should be shown by default for a specific item type. + /// Results are sorted by DefaultDisplayOrder. + /// + /// The item type. + /// Fields shown by default for the item type. + public static IEnumerable GetDefaultFieldsForItemType(string itemType) + { + return Fields.Values + .Where(f => f.ApplicableToTypes.TryGetValue(itemType, out var config) && config.ShowByDefault) + .OrderBy(f => f.DefaultDisplayOrder); + } + + /// + /// Get system fields that are NOT shown by default for a specific item type. + /// These are the fields that can be added via an "add field" button. + /// Results are sorted by DefaultDisplayOrder. + /// + /// The item type. + /// Optional fields for the item type. + public static IEnumerable GetOptionalFieldsForItemType(string itemType) + { + return Fields.Values + .Where(f => f.ApplicableToTypes.TryGetValue(itemType, out var config) && !config.ShowByDefault) + .OrderBy(f => f.DefaultDisplayOrder); + } + + /// + /// Check if a field key matches a known system field prefix. + /// + /// The field key to check. + /// True if the field key has a system field prefix. + public static bool IsSystemFieldPrefix(string fieldKey) + { + return ${prefixChecks}; + } +} +`; + + return header + fieldEntries + methods; +} + +/** + * Convert field key to C# property name (e.g., 'login.username' -> 'LoginUsername') + */ +function fieldKeyToPropertyName(fieldKey) { + return fieldKey + .split('.') + .map(part => part.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('')) + .join(''); +} + /** * Ensure directory exists */ @@ -162,8 +614,7 @@ function ensureDir(filePath) { * Main execution */ function main() { - - // Read TypeScript source + // Read TypeScript FieldKey source if (!fs.existsSync(TS_SOURCE)) { throw new Error(`Source file not found: ${TS_SOURCE}`); } @@ -175,20 +626,59 @@ function main() { throw new Error('No field keys found in source file'); } - // Generate C# + // Read TypeScript SystemFieldRegistry source + if (!fs.existsSync(TS_REGISTRY_SOURCE)) { + throw new Error(`Source file not found: ${TS_REGISTRY_SOURCE}`); + } + + const tsRegistryContent = fs.readFileSync(TS_REGISTRY_SOURCE, 'utf8'); + + // Parse types dynamically from TypeScript + const categories = parseFieldCategories(tsRegistryContent); + const itemTypeFieldConfigProps = parseItemTypeFieldConfig(tsRegistryContent); + const systemFieldDefProps = parseSystemFieldDefinition(tsRegistryContent); + const systemFields = parseSystemFieldRegistry(tsRegistryContent); + + if (Object.keys(systemFields).length === 0) { + throw new Error('No system fields found in registry source file'); + } + + console.log(`Parsed ${Object.keys(fieldKeys).length} field keys`); + console.log(`Parsed ${categories.length} field categories: ${categories.join(', ')}`); + console.log(`Parsed ${itemTypeFieldConfigProps.length} ItemTypeFieldConfig properties`); + console.log(`Parsed ${systemFieldDefProps.length} SystemFieldDefinition properties`); + console.log(`Parsed ${Object.keys(systemFields).length} system field definitions`); + + // Generate C# FieldKey ensureDir(CS_OUTPUT); const csContent = generateCSharp(fieldKeys); fs.writeFileSync(CS_OUTPUT, csContent, 'utf8'); + console.log(`Generated: ${CS_OUTPUT}`); + + // Generate C# SystemFieldRegistry + ensureDir(CS_REGISTRY_OUTPUT); + const csRegistryContent = generateCSharpSystemFieldRegistry( + systemFields, + categories, + itemTypeFieldConfigProps, + systemFieldDefProps + ); + fs.writeFileSync(CS_REGISTRY_OUTPUT, csRegistryContent, 'utf8'); + console.log(`Generated: ${CS_REGISTRY_OUTPUT}`); // Generate Swift ensureDir(SWIFT_OUTPUT); const swiftContent = generateSwift(fieldKeys); fs.writeFileSync(SWIFT_OUTPUT, swiftContent, 'utf8'); + console.log(`Generated: ${SWIFT_OUTPUT}`); // Generate Kotlin ensureDir(KOTLIN_OUTPUT); const kotlinContent = generateKotlin(fieldKeys); fs.writeFileSync(KOTLIN_OUTPUT, kotlinContent, 'utf8'); + console.log(`Generated: ${KOTLIN_OUTPUT}`); + + console.log('\nCode generation complete!'); } main(); diff --git a/core/rust/Cargo.toml b/core/rust/Cargo.toml index 4039d3369..751a4e285 100644 --- a/core/rust/Cargo.toml +++ b/core/rust/Cargo.toml @@ -20,6 +20,8 @@ default = [] uniffi = ["dep:uniffi"] # Feature for WASM builds (browser extension) wasm = ["dep:wasm-bindgen", "dep:serde-wasm-bindgen", "dep:console_error_panic_hook"] +# Feature for C FFI exports (.NET P/Invoke) +ffi = [] [dependencies] # Serialization - core dependency for JSON handling diff --git a/core/rust/build.sh b/core/rust/build.sh index bb7de702d..f552cca85 100755 --- a/core/rust/build.sh +++ b/core/rust/build.sh @@ -17,9 +17,11 @@ cd "$SCRIPT_DIR" # Output directories DIST_DIR="$SCRIPT_DIR/dist" WASM_DIR="$DIST_DIR/wasm" +DOTNET_DIR="$DIST_DIR/dotnet" # Target directories in consumer apps BROWSER_EXT_DIST="$SCRIPT_DIR/../../apps/browser-extension/src/utils/dist/core/rust" +BLAZOR_CLIENT_DIST="$SCRIPT_DIR/../../apps/server/AliasVault.Client/wwwroot/wasm" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE} AliasVault Rust Core Build Script${NC}" @@ -47,6 +49,7 @@ echo -e " Rust version: ${GREEN}$RUST_VERSION${NC}" # Build mode selection BUILD_ALL=false BUILD_BROWSER=false +BUILD_DOTNET=false FAST_MODE=false # Parse arguments @@ -56,6 +59,15 @@ while [[ $# -gt 0 ]]; do BUILD_BROWSER=true shift ;; + --dotnet) + BUILD_DOTNET=true + shift + ;; + --all) + BUILD_BROWSER=true + BUILD_DOTNET=true + shift + ;; --fast|--dev) FAST_MODE=true echo -e "${YELLOW}Fast/dev mode enabled${NC}" @@ -65,7 +77,9 @@ while [[ $# -gt 0 ]]; do echo "Usage: $0 [options]" echo "" echo "Target options:" - echo " --browser Build WASM for browser extension" + echo " --browser Build WASM for browser extension and Blazor WASM client" + echo " --dotnet Build native library for .NET server-side use (macOS/Linux/Windows)" + echo " --all Build all targets" echo "" echo "Speed options:" echo " --fast, --dev Faster builds (for development)" @@ -73,9 +87,6 @@ while [[ $# -gt 0 ]]; do echo "Other options:" echo " --help Show this help message" echo "" - echo "Examples:" - echo " ./build.sh --browser # Build WASM for browser extension" - echo " ./build.sh --browser --fast # Fast dev build" exit 0 ;; *) @@ -86,11 +97,12 @@ while [[ $# -gt 0 ]]; do done # If no targets specified, show help -if ! $BUILD_BROWSER; then +if ! $BUILD_BROWSER && ! $BUILD_DOTNET; then echo "No target specified. Use --help for usage." echo "" echo "Quick start:" echo " ./build.sh --browser # Build for browser extension" + echo " ./build.sh --dotnet # Build for .NET" exit 0 fi @@ -163,9 +175,99 @@ README_EOF echo -e "${GREEN}Distributed to: $BROWSER_EXT_DIST${NC}" ls -lh "$BROWSER_EXT_DIST/" + + # Also distribute to Blazor client + echo "" + echo -e "${BLUE}Distributing to Blazor client...${NC}" + rm -rf "$BLAZOR_CLIENT_DIST" + mkdir -p "$BLAZOR_CLIENT_DIST" + cp "$WASM_DIR"/aliasvault_core_bg.wasm "$BLAZOR_CLIENT_DIST/" + cp "$WASM_DIR"/aliasvault_core.js "$BLAZOR_CLIENT_DIST/" + + echo -e "${GREEN}Distributed to: $BLAZOR_CLIENT_DIST${NC}" + ls -lh "$BLAZOR_CLIENT_DIST/" fi } +# ============================================ +# .NET Build (Native Library with FFI) +# ============================================ +build_dotnet() { + echo "" + echo -e "${BLUE}Building native library for .NET...${NC}" + + local start_time=$(date +%s) + + # Detect current platform + local os_name + local arch_name + local lib_name + local target_dir + + case "$(uname -s)" in + Darwin) + os_name="macos" + lib_name="libaliasvault_core.dylib" + ;; + Linux) + os_name="linux" + lib_name="libaliasvault_core.so" + ;; + MINGW*|MSYS*|CYGWIN*) + os_name="windows" + lib_name="aliasvault_core.dll" + ;; + *) + echo -e "${RED}Unsupported OS: $(uname -s)${NC}" + exit 1 + ;; + esac + + case "$(uname -m)" in + x86_64|amd64) + arch_name="x64" + ;; + arm64|aarch64) + arch_name="arm64" + ;; + *) + arch_name="$(uname -m)" + ;; + esac + + target_dir="$DOTNET_DIR/${os_name}-${arch_name}" + mkdir -p "$target_dir" + + echo -e " Platform: ${YELLOW}${os_name}-${arch_name}${NC}" + + # Build with cargo + echo -e " Running cargo build..." + if $FAST_MODE; then + cargo build --features ffi + local cargo_target="target/debug" + else + cargo build --release --features ffi + local cargo_target="target/release" + fi + + # Copy the library + if [ -f "$cargo_target/$lib_name" ]; then + cp "$cargo_target/$lib_name" "$target_dir/" + local lib_size + lib_size=$(ls -lh "$target_dir/$lib_name" | awk '{print $5}') + echo -e "${GREEN}Native library built! ${NC}" + echo -e " Output: ${YELLOW}$target_dir/$lib_name${NC}" + echo -e " Size: ${YELLOW}$lib_size${NC}" + else + echo -e "${RED}Build failed: $lib_name not found${NC}" + exit 1 + fi + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + echo -e "${GREEN}.NET build complete! (${duration}s)${NC}" +} + # ============================================ # Main Build Process # ============================================ @@ -176,6 +278,13 @@ if $BUILD_BROWSER; then distribute_browser fi +if $BUILD_DOTNET; then + build_dotnet + # Note: dotnet native libs are built to dist/dotnet/ but not distributed + # Blazor WASM uses the WASM module via JS interop instead + echo -e "${YELLOW}Note: Native library built to dist/dotnet/ (for server-side .NET use)${NC}" +fi + TOTAL_END=$(date +%s) TOTAL_DURATION=$((TOTAL_END - TOTAL_START)) diff --git a/core/rust/src/ffi.rs b/core/rust/src/ffi.rs new file mode 100644 index 000000000..3ef35c036 --- /dev/null +++ b/core/rust/src/ffi.rs @@ -0,0 +1,181 @@ +//! C FFI exports for .NET P/Invoke. +//! +//! These functions provide a C-compatible interface for calling Rust functions from C#. +//! All functions use JSON strings for input/output to simplify marshalling. + +use std::ffi::{c_char, CStr, CString}; +use std::ptr; + +use crate::credential_matcher::{filter_credentials, CredentialMatcherInput}; +use crate::merge::{merge_vaults, MergeInput, SYNCABLE_TABLE_NAMES}; + +/// Merge two vaults using LWW strategy. +/// +/// # Safety +/// +/// - `input_json` must be a valid null-terminated C string +/// - The returned pointer must be freed by calling `free_string` +/// +/// # Returns +/// +/// A null-terminated C string containing the JSON result (MergeOutput). +/// Returns null on error. +#[no_mangle] +pub unsafe extern "C" fn merge_vaults_ffi(input_json: *const c_char) -> *mut c_char { + if input_json.is_null() { + return ptr::null_mut(); + } + + let c_str = match CStr::from_ptr(input_json).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + }; + + let input: MergeInput = match serde_json::from_str(c_str) { + Ok(i) => i, + Err(e) => { + return create_error_response(&format!("Failed to parse input: {}", e)); + } + }; + + let output = match merge_vaults(input) { + Ok(o) => o, + Err(e) => { + return create_error_response(&format!("Merge failed: {}", e)); + } + }; + + match serde_json::to_string(&output) { + Ok(json) => string_to_c_char(json), + Err(e) => create_error_response(&format!("Failed to serialize output: {}", e)), + } +} + +/// Filter credentials for autofill. +/// +/// # Safety +/// +/// - `input_json` must be a valid null-terminated C string +/// - The returned pointer must be freed by calling `free_string` +/// +/// # Returns +/// +/// A null-terminated C string containing the JSON result (CredentialMatcherOutput). +/// Returns null on error. +#[no_mangle] +pub unsafe extern "C" fn filter_credentials_ffi(input_json: *const c_char) -> *mut c_char { + if input_json.is_null() { + return ptr::null_mut(); + } + + let c_str = match CStr::from_ptr(input_json).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + }; + + let input: CredentialMatcherInput = match serde_json::from_str(c_str) { + Ok(i) => i, + Err(e) => { + return create_error_response(&format!("Failed to parse input: {}", e)); + } + }; + + let output = filter_credentials(input); + + match serde_json::to_string(&output) { + Ok(json) => string_to_c_char(json), + Err(e) => create_error_response(&format!("Failed to serialize output: {}", e)), + } +} + +/// Get the list of syncable table names as a JSON array. +/// +/// # Safety +/// +/// - The returned pointer must be freed by calling `free_string` +/// +/// # Returns +/// +/// A null-terminated C string containing a JSON array of table names. +#[no_mangle] +pub extern "C" fn get_syncable_table_names_ffi() -> *mut c_char { + let names: Vec<&str> = SYNCABLE_TABLE_NAMES.iter().map(|s| *s).collect(); + match serde_json::to_string(&names) { + Ok(json) => string_to_c_char(json), + Err(_) => ptr::null_mut(), + } +} + +/// Free a string that was allocated by Rust. +/// +/// # Safety +/// +/// - `s` must be a pointer that was returned by one of the FFI functions +/// - This function must only be called once per pointer +/// - After calling this function, the pointer is invalid +#[no_mangle] +pub unsafe extern "C" fn free_string(s: *mut c_char) { + if !s.is_null() { + drop(CString::from_raw(s)); + } +} + +/// Convert a Rust string to a C string pointer. +fn string_to_c_char(s: String) -> *mut c_char { + match CString::new(s) { + Ok(c_string) => c_string.into_raw(), + Err(_) => ptr::null_mut(), + } +} + +/// Create an error response JSON string. +fn create_error_response(message: &str) -> *mut c_char { + let error_json = format!(r#"{{"success":false,"error":"{}"}}"#, message.replace('"', r#"\""#)); + string_to_c_char(error_json) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + + #[test] + fn test_get_syncable_table_names() { + let result = get_syncable_table_names_ffi(); + assert!(!result.is_null()); + + unsafe { + let c_str = CStr::from_ptr(result); + let json = c_str.to_str().unwrap(); + let names: Vec = serde_json::from_str(json).unwrap(); + assert!(names.contains(&"Items".to_string())); + assert!(names.contains(&"FieldValues".to_string())); + free_string(result); + } + } + + #[test] + fn test_null_input() { + unsafe { + let result = merge_vaults_ffi(ptr::null()); + assert!(result.is_null()); + + let result = filter_credentials_ffi(ptr::null()); + assert!(result.is_null()); + } + } + + #[test] + fn test_invalid_json_input() { + let invalid_json = CString::new("not valid json").unwrap(); + unsafe { + let result = merge_vaults_ffi(invalid_json.as_ptr()); + assert!(!result.is_null()); + + let c_str = CStr::from_ptr(result); + let json = c_str.to_str().unwrap(); + assert!(json.contains("error")); + free_string(result); + } + } +} diff --git a/core/rust/src/lib.rs b/core/rust/src/lib.rs index 76d4764d0..ac75ef8b3 100644 --- a/core/rust/src/lib.rs +++ b/core/rust/src/lib.rs @@ -41,6 +41,10 @@ pub mod wasm; #[cfg(feature = "wasm")] pub use wasm::*; +// C FFI exports for .NET P/Invoke +#[cfg(feature = "ffi")] +pub mod ffi; + // UniFFI scaffolding #[cfg(feature = "uniffi")] uniffi::setup_scaffolding!(); diff --git a/core/rust/src/merge/mod.rs b/core/rust/src/merge/mod.rs index 9f63077a8..7d10c48f4 100644 --- a/core/rust/src/merge/mod.rs +++ b/core/rust/src/merge/mod.rs @@ -231,29 +231,10 @@ fn merge_table_by_composite_key( ) -> Vec { let mut statements: Vec = Vec::new(); - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - log(&format!( - "[merge_composite] Processing {} - local={} records, server={} records, key_cols={:?}", - table_name, - local_records.len(), - server_records.len(), - key_columns - )); - } - // Create map of server records by composite key let mut server_map: HashMap = HashMap::new(); for record in server_records { let key = get_composite_key(record, key_columns); - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - let value = record.get("Value").and_then(|v| v.as_str()).unwrap_or("(no value)"); - let updated = record.get("UpdatedAt").and_then(|v| v.as_str()).unwrap_or("(no ts)"); - log(&format!("[merge_composite] SERVER record: key={}, value={}, updated={}", key, value, updated)); - } // Keep the one with latest UpdatedAt if duplicate keys if let Some(existing) = server_map.get(&key) { if get_updated_at(record) > get_updated_at(existing) { @@ -273,14 +254,6 @@ fn merge_table_by_composite_key( None => continue, }; - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - let value = local_record.get("Value").and_then(|v| v.as_str()).unwrap_or("(no value)"); - let updated = local_record.get("UpdatedAt").and_then(|v| v.as_str()).unwrap_or("(no ts)"); - log(&format!("[merge_composite] LOCAL record: key={}, value={}, updated={}", composite_key, value, updated)); - } - if let Some(server_record) = server_map.get(&composite_key) { // Record exists in both - compare UpdatedAt let local_ts = get_updated_at(local_record); @@ -292,44 +265,24 @@ fn merge_table_by_composite_key( stats.conflicts += 1; stats.records_from_server += 1; if let Some(stmt) = generate_update_sql(table_name, server_record, &local_id) { - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - log(&format!("[merge_composite] SERVER WINS: key={}", composite_key)); - } statements.push(stmt); } } _ => { // Local wins - no action needed stats.records_from_local += 1; - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - log(&format!("[merge_composite] LOCAL WINS: key={}", composite_key)); - } } } server_map.remove(&composite_key); } else { // Only in local - no action needed stats.records_created_locally += 1; - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - log(&format!("[merge_composite] LOCAL ONLY: key={}", composite_key)); - } } } // Server-only records (by composite key) - generate INSERTs for (_key, server_record) in &server_map { stats.records_inserted += 1; - #[cfg(feature = "wasm")] - { - use crate::wasm::log; - log(&format!("[merge_composite] SERVER ONLY (INSERT): key={}", _key)); - } if let Some(stmt) = generate_insert_sql(table_name, server_record) { statements.push(stmt); }