diff --git a/Directory.Packages.props b/Directory.Packages.props
index 54ec2f0..0ccc88e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -10,6 +10,6 @@
-
+
\ No newline at end of file
diff --git a/examples/SimpleReportServer/HelloReport.razor b/examples/SimpleReportServer/HelloReport.razor
index c0dc338..95399b9 100644
--- a/examples/SimpleReportServer/HelloReport.razor
+++ b/examples/SimpleReportServer/HelloReport.razor
@@ -1,3 +1,4 @@
+
Hello @Data.Name
@code {
diff --git a/examples/SimpleReportServer/Program.cs b/examples/SimpleReportServer/Program.cs
index d7aefb9..4425dea 100644
--- a/examples/SimpleReportServer/Program.cs
+++ b/examples/SimpleReportServer/Program.cs
@@ -2,6 +2,7 @@
using BlazorReports.Models;
using ExampleTemplates.Reports;
using SimpleReportServer;
+using SimpleReportServer.Reports;
var builder = WebApplication.CreateSlimBuilder(args);
@@ -19,6 +20,10 @@
var reportsGroup = app.MapGroup("reports");
+reportsGroup.MapBlazorReport(opts =>
+{
+ opts.JavascriptSettings.WaitForJavascriptCompletedSignal = true;
+});
reportsGroup.MapBlazorReport();
reportsGroup.MapBlazorReport(opts =>
{
diff --git a/examples/SimpleReportServer/Reports/SimpleJsAsyncReport.razor b/examples/SimpleReportServer/Reports/SimpleJsAsyncReport.razor
new file mode 100644
index 0000000..46eb0d0
--- /dev/null
+++ b/examples/SimpleReportServer/Reports/SimpleJsAsyncReport.razor
@@ -0,0 +1,22 @@
+
+
+
+@code {
+ [Parameter]
+ public required SimpleJsAsyncReportData Data { get; set; }
+
+}
diff --git a/examples/SimpleReportServer/Reports/SimpleJsAsyncReportData.cs b/examples/SimpleReportServer/Reports/SimpleJsAsyncReportData.cs
new file mode 100644
index 0000000..1e74068
--- /dev/null
+++ b/examples/SimpleReportServer/Reports/SimpleJsAsyncReportData.cs
@@ -0,0 +1,6 @@
+namespace SimpleReportServer.Reports;
+
+public record SimpleJsAsyncReportData(int TimeoutInSeconds)
+{
+ public TimeSpan TimeSpan => TimeSpan.FromSeconds(TimeoutInSeconds);
+};
diff --git a/examples/SimpleReportServer/wwwroot/js/HelloJavascriptData.js b/examples/SimpleReportServer/wwwroot/js/HelloJavascriptData.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/BlazorReports.Components/BlazorReportsTemplate.razor b/src/BlazorReports.Components/BlazorReportsTemplate.razor
index d0c4e6d..4c524ca 100644
--- a/src/BlazorReports.Components/BlazorReportsTemplate.razor
+++ b/src/BlazorReports.Components/BlazorReportsTemplate.razor
@@ -1,29 +1,90 @@
-
+
+
-
+
+
+ @foreach (var script in Scripts)
+ {
+
+ }
-
@code {
- ///
- /// The base styles to be applied to the component.
- ///
- [Parameter]
- public string BaseStyles { get; set; } = "";
- ///
- /// The type of the component to be rendered in the html body.
- ///
- [Parameter]
- public Type ChildComponentType { get; set; } = null!;
- ///
- /// The parameters to be passed to the child component.
- ///
- [Parameter]
- public IDictionary ChildComponentParameters { get; set; } = null!;
+ ///
+ /// The base styles to be applied to the component.
+ ///
+ [Parameter]
+ public string BaseStyles { get; set; } = "";
+
+ ///
+ /// The type of the component to be rendered in the html body.
+ ///
+ [Parameter]
+ public Type ChildComponentType { get; set; } = null!;
+
+ ///
+ /// The parameters to be passed to the child component.
+ ///
+ [Parameter]
+ public IDictionary ChildComponentParameters { get; set; } = null!;
+
+ ///
+ /// The scripts to be included in the html body.
+ ///
+ [Parameter]
+ public Dictionary Scripts { get; set; } = new Dictionary();
+
+ ///
+ /// The name of the JavaScript signal used to indicate when the report is ready.
+ ///
+ [Parameter]
+ public string ReportIsReadySignal { get; set; } = "reportIsReady";
+
}
diff --git a/src/BlazorReports/Extensions/ReportExtensions.cs b/src/BlazorReports/Extensions/ReportExtensions.cs
index 3d360a6..e82ac78 100644
--- a/src/BlazorReports/Extensions/ReportExtensions.cs
+++ b/src/BlazorReports/Extensions/ReportExtensions.cs
@@ -110,7 +110,8 @@ CancellationToken token
_ => (int?)null,
_ => StatusCodes.Status503ServiceUnavailable,
_ => StatusCodes.Status499ClientClosedRequest,
- _ => StatusCodes.Status500InternalServerError
+ _ => StatusCodes.Status500InternalServerError,
+ _ => StatusCodes.Status408RequestTimeout
);
if (errorStatusCode is not null)
@@ -121,6 +122,7 @@ CancellationToken token
}
)
.Produces(200, blazorReport.GetContentType())
+ .Produces(StatusCodes.Status408RequestTimeout)
.Produces(StatusCodes.Status503ServiceUnavailable);
}
@@ -174,7 +176,8 @@ CancellationToken token
_ => (int?)null,
_ => StatusCodes.Status503ServiceUnavailable,
_ => StatusCodes.Status499ClientClosedRequest,
- _ => StatusCodes.Status500InternalServerError
+ _ => StatusCodes.Status500InternalServerError,
+ _ => StatusCodes.Status408RequestTimeout
);
if (errorStatusCode is not null)
@@ -185,6 +188,7 @@ CancellationToken token
}
)
.Produces(200, blazorReport.GetContentType())
+ .Produces(StatusCodes.Status408RequestTimeout)
.Produces(StatusCodes.Status503ServiceUnavailable);
}
diff --git a/src/BlazorReports/Extensions/ServiceCollectionExtensions.cs b/src/BlazorReports/Extensions/ServiceCollectionExtensions.cs
index 3eda7c2..8ba7892 100644
--- a/src/BlazorReports/Extensions/ServiceCollectionExtensions.cs
+++ b/src/BlazorReports/Extensions/ServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using BlazorReports.Services;
using BlazorReports.Services.BrowserServices;
using BlazorReports.Services.BrowserServices.Factories;
+using BlazorReports.Services.JavascriptServices;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
@@ -26,6 +27,8 @@ public static IServiceCollection AddBlazorReports(
{
services.Configure(options ?? (_ => { }));
services.AddSingleton();
+ services.AddSingleton();
+
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
diff --git a/src/BlazorReports/Models/BlazorReport.cs b/src/BlazorReports/Models/BlazorReport.cs
index 5359173..caaaeb6 100644
--- a/src/BlazorReports/Models/BlazorReport.cs
+++ b/src/BlazorReports/Models/BlazorReport.cs
@@ -44,4 +44,9 @@ public class BlazorReport
/// The page settings to use for the report.
///
public BlazorReportsPageSettings? PageSettings { get; set; }
+
+ ///
+ /// The current report javascript settings.
+ ///
+ public required BlazorReportCurrentReportJavascriptSettings CurrentReportJavascriptSettings { get; set; }
}
diff --git a/src/BlazorReports/Models/BlazorReportCurrentReportJavascriptSettings.cs b/src/BlazorReports/Models/BlazorReportCurrentReportJavascriptSettings.cs
new file mode 100644
index 0000000..5f77e4a
--- /dev/null
+++ b/src/BlazorReports/Models/BlazorReportCurrentReportJavascriptSettings.cs
@@ -0,0 +1,32 @@
+namespace BlazorReports.Models;
+
+///
+/// Settings for the internal javascript api
+///
+public class BlazorReportCurrentReportJavascriptSettings
+{
+ ///
+ /// The signal that the report is ready
+ ///
+ public string ReportIsReadySignal { get; set; } = default!;
+
+ ///
+ /// Decides if the report should wait for the complete signal in the javascript
+ ///
+ public bool WaitForJavascriptCompletedSignal { get; set; }
+
+ ///
+ /// The amount of time a reports javascript can take until it is considered to have timed out
+ ///
+ public TimeSpan? WaitForCompletedSignalTimeout { get; set; }
+
+ ///
+ /// The default settings for the current report javascript settings
+ ///
+ public static BlazorReportCurrentReportJavascriptSettings Default =>
+ new BlazorReportCurrentReportJavascriptSettings
+ {
+ WaitForJavascriptCompletedSignal = false,
+ WaitForCompletedSignalTimeout = TimeSpan.FromSeconds(3),
+ };
+}
diff --git a/src/BlazorReports/Models/BlazorReportGlobalJavascriptSettings.cs b/src/BlazorReports/Models/BlazorReportGlobalJavascriptSettings.cs
new file mode 100644
index 0000000..af807f5
--- /dev/null
+++ b/src/BlazorReports/Models/BlazorReportGlobalJavascriptSettings.cs
@@ -0,0 +1,24 @@
+namespace BlazorReports.Models;
+
+///
+///
+///
+public class BlazorReportGlobalJavascriptSettings
+{
+ ///
+ /// The signal that the report is ready
+ ///
+ public string ReportIsReadySignal { get; set; } = "reportIsReady";
+
+ ///
+ /// Decides if the report should wait for the complete signal in the javascript.
+ /// (Default: false)
+ ///
+ public bool WaitForJavascriptCompletedSignal { get; set; }
+
+ ///
+ /// The amount of time a reports javascript can take until it is considered to have timed out
+ /// (Default: null)
+ ///
+ public TimeSpan WaitForCompletedSignalTimeout { get; set; } = TimeSpan.FromSeconds(3);
+}
diff --git a/src/BlazorReports/Models/BlazorReportRegistrationOptions.cs b/src/BlazorReports/Models/BlazorReportRegistrationOptions.cs
index 726416b..da55af3 100644
--- a/src/BlazorReports/Models/BlazorReportRegistrationOptions.cs
+++ b/src/BlazorReports/Models/BlazorReportRegistrationOptions.cs
@@ -29,4 +29,9 @@ public class BlazorReportRegistrationOptions
/// Settings for generating a PDF
///
public BlazorReportsPageSettings PageSettings { get; set; } = new();
+
+ ///
+ /// Javascript api settings
+ ///
+ public BlazorReportCurrentReportJavascriptSettings JavascriptSettings { get; set; } = new();
}
diff --git a/src/BlazorReports/Models/BlazorReportRegistry.cs b/src/BlazorReports/Models/BlazorReportRegistry.cs
index fec0360..9e24b46 100644
--- a/src/BlazorReports/Models/BlazorReportRegistry.cs
+++ b/src/BlazorReports/Models/BlazorReportRegistry.cs
@@ -85,6 +85,8 @@ public BlazorReport AddReport(BlazorReportRegistrationOptions? options = null
Component = typeof(T),
Data = null,
PageSettings = options?.PageSettings,
+ CurrentReportJavascriptSettings =
+ options?.JavascriptSettings ?? BlazorReportCurrentReportJavascriptSettings.Default,
};
if (!string.IsNullOrEmpty(options?.BaseStylesPath))
{
@@ -137,6 +139,8 @@ public BlazorReport AddReport(BlazorReportRegistrationOptions? options =
Component = typeof(T),
Data = typeof(TD),
PageSettings = options?.PageSettings,
+ CurrentReportJavascriptSettings =
+ options?.JavascriptSettings ?? BlazorReportCurrentReportJavascriptSettings.Default,
};
if (!string.IsNullOrEmpty(options?.BaseStylesPath))
{
diff --git a/src/BlazorReports/Models/BlazorReportsOptions.cs b/src/BlazorReports/Models/BlazorReportsOptions.cs
index 86dbebc..4a864c4 100644
--- a/src/BlazorReports/Models/BlazorReportsOptions.cs
+++ b/src/BlazorReports/Models/BlazorReportsOptions.cs
@@ -24,4 +24,9 @@ public class BlazorReportsOptions
/// Settings for generating a PDF
///
public BlazorReportsPageSettings PageSettings { get; set; } = new();
+
+ ///
+ /// Javascript api settings
+ ///
+ public BlazorReportGlobalJavascriptSettings GlobalJavascriptSettings { get; set; } = new();
}
diff --git a/src/BlazorReports/Services/BrowserServices/Browser.cs b/src/BlazorReports/Services/BrowserServices/Browser.cs
index ed94142..fef96e1 100644
--- a/src/BlazorReports/Services/BrowserServices/Browser.cs
+++ b/src/BlazorReports/Services/BrowserServices/Browser.cs
@@ -21,6 +21,7 @@ internal sealed class Browser(
DirectoryInfo dataDirectory,
Connection connection,
BlazorReportsBrowserOptions browserOptions,
+ BlazorReportGlobalJavascriptSettings globalJavascriptSettings,
ILogger logger,
IBrowserPageFactory browserPageFactory
) : IAsyncDisposable
@@ -30,11 +31,18 @@ IBrowserPageFactory browserPageFactory
private readonly SemaphoreSlim _poolLock = new(1, 1);
public async ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
string html,
BlazorReportsPageSettings pageSettings,
+ BlazorReportCurrentReportJavascriptSettings currentReportJavascriptSettings,
CancellationToken cancellationToken
)
{
@@ -92,6 +100,24 @@ out browserPage
try
{
await browserPage.DisplayHtml(html, cancellationToken);
+ var shouldReportAwaitJavascript =
+ currentReportJavascriptSettings.WaitForJavascriptCompletedSignal
+ || globalJavascriptSettings.WaitForJavascriptCompletedSignal;
+ if (shouldReportAwaitJavascript)
+ {
+ TimeSpan globalTimeout = globalJavascriptSettings.WaitForCompletedSignalTimeout;
+ TimeSpan? currentReportTimeout =
+ currentReportJavascriptSettings.WaitForCompletedSignalTimeout;
+
+ TimeSpan timeout = currentReportTimeout ?? globalTimeout;
+
+ var didNotHitTimeOut = await browserPage.WaitForJsFlagAsync(timeout, cancellationToken);
+ if (!didNotHitTimeOut)
+ {
+ return new CompletedSignalTimeoutProblem();
+ }
+ }
+
await browserPage.ConvertPageToPdf(pipeWriter, pageSettings, cancellationToken);
}
catch (Exception e)
diff --git a/src/BlazorReports/Services/BrowserServices/BrowserPage.cs b/src/BlazorReports/Services/BrowserServices/BrowserPage.cs
index e2df393..852f1cf 100644
--- a/src/BlazorReports/Services/BrowserServices/BrowserPage.cs
+++ b/src/BlazorReports/Services/BrowserServices/BrowserPage.cs
@@ -70,6 +70,145 @@ await connection.SendAsync(
);
}
+ ///
+ /// Checks if a JavaScript flag is set
+ ///
+ /// Flag to be checked
+ /// cancellation token
+ /// Returns the flag current value
+ internal async Task CheckIfFlagIsSetAsync(
+ string flagName,
+ CancellationToken stoppingToken = default
+ )
+ {
+ await connection.ConnectAsync(stoppingToken);
+
+ BrowserMessage evaluateMessage = new("Runtime.evaluate");
+ evaluateMessage.Parameters.Add("expression", $"window.{flagName} === true");
+ evaluateMessage.Parameters.Add("returnByValue", true);
+ evaluateMessage.Parameters.Add("awaitPromise", true);
+
+ bool isFlagSet = false;
+
+ await connection.SendAsync(
+ evaluateMessage,
+ RuntimeEvaluateResponseSerializationContext
+ .Default
+ .BrowserResultResponseRuntimeEvaluateResponse,
+ evaluateResponse =>
+ {
+ if (!evaluateResponse.Result.WasThrown && evaluateResponse.Result.Result?.Type == "boolean")
+ {
+ isFlagSet = evaluateResponse.Result.Result?.Value?.GetBoolean() ?? false;
+ }
+ },
+ stoppingToken
+ );
+
+ return isFlagSet;
+ }
+
+ ///
+ /// Waits for a JavaScript flag to be set
+ ///
+ ///
+ ///
+ ///
+ internal async Task WaitForJsFlagAsync(TimeSpan timeout, CancellationToken stoppingToken)
+ {
+ string script = $"waitForSignal({timeout.TotalMilliseconds})";
+
+ var result = await EvaluateJavaScriptAsync(script, stoppingToken);
+
+ return result == "Signal received";
+ }
+
+ ///
+ /// Checks if a JavaScript flag exists
+ ///
+ ///
+ ///
+ ///
+ internal async Task DoesJsFlagExistAsync(
+ string flagName,
+ CancellationToken stoppingToken = default
+ )
+ {
+ await connection.ConnectAsync(stoppingToken);
+
+ // JavaScript Expression: Check if the flag exists in the window object
+ string jsCheck = $"typeof window.{flagName} !== 'undefined'";
+
+ BrowserMessage evaluateMessage = new("Runtime.evaluate");
+ evaluateMessage.Parameters.Add("expression", jsCheck);
+ evaluateMessage.Parameters.Add("returnByValue", true);
+ evaluateMessage.Parameters.Add("awaitPromise", true);
+
+ bool flagExists = false;
+
+ await connection.SendAsync(
+ evaluateMessage,
+ RuntimeEvaluateResponseSerializationContext
+ .Default
+ .BrowserResultResponseRuntimeEvaluateResponse,
+ evaluateResponse =>
+ {
+ // If JavaScript returns "true", it means the flag exists
+ if (evaluateResponse.Result.Result?.Type == "boolean")
+ {
+ flagExists = evaluateResponse.Result.Result?.Value?.GetBoolean() ?? false;
+ }
+ },
+ stoppingToken
+ );
+
+ return flagExists;
+ }
+
+ ///
+ /// Evaluates JavaScript code
+ ///
+ ///
+ ///
+ ///
+ internal async Task EvaluateJavaScriptAsync(
+ string script,
+ CancellationToken stoppingToken
+ )
+ {
+ var evaluateMessage = new BrowserMessage("Runtime.evaluate");
+ evaluateMessage.Parameters.Add("expression", script);
+ evaluateMessage.Parameters.Add("returnByValue", true);
+ evaluateMessage.Parameters.Add("awaitPromise", true);
+
+ string? evaluatedValue = null;
+
+ await connection.SendAsync(
+ evaluateMessage,
+ RuntimeEvaluateResponseSerializationContext
+ .Default
+ .BrowserResultResponseRuntimeEvaluateResponse,
+ evaluateResponse =>
+ {
+ if (
+ evaluateResponse.Result.WasThrown && evaluateResponse.Result.ExceptionDetails is not null
+ )
+ {
+ // Handle JS error
+ return;
+ }
+
+ if (evaluateResponse.Result.Result?.Type == "string")
+ {
+ evaluatedValue = evaluateResponse.Result.Result?.Value?.GetString();
+ }
+ },
+ stoppingToken
+ );
+
+ return evaluatedValue;
+ }
+
internal async ValueTask ConvertPageToPdf(
PipeWriter pipeWriter,
BlazorReportsPageSettings pageSettings,
diff --git a/src/BlazorReports/Services/BrowserServices/BrowserService.cs b/src/BlazorReports/Services/BrowserServices/BrowserService.cs
index 39f3ade..26df2ea 100644
--- a/src/BlazorReports/Services/BrowserServices/BrowserService.cs
+++ b/src/BlazorReports/Services/BrowserServices/BrowserService.cs
@@ -48,14 +48,22 @@ IBrowserFactory browserFactory
/// The pipe writer to write the report to
/// The HTML to use in the report
/// The page settings to use in the report
+ /// The current report javascript settings
/// The cancellation token
/// The result of the operation
public async ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
string html,
BlazorReportsPageSettings pageSettings,
+ BlazorReportCurrentReportJavascriptSettings currentReportJavascriptSettings,
CancellationToken cancellationToken
)
{
@@ -80,7 +88,13 @@ out var browser
return browserStartProblem;
}
- return await browser.GenerateReport(pipeWriter, html, pageSettings, cancellationToken);
+ return await browser.GenerateReport(
+ pipeWriter,
+ html,
+ pageSettings,
+ currentReportJavascriptSettings,
+ cancellationToken
+ );
}
///
diff --git a/src/BlazorReports/Services/BrowserServices/Factories/BrowserFactory.cs b/src/BlazorReports/Services/BrowserServices/Factories/BrowserFactory.cs
index 4472519..06a0df3 100644
--- a/src/BlazorReports/Services/BrowserServices/Factories/BrowserFactory.cs
+++ b/src/BlazorReports/Services/BrowserServices/Factories/BrowserFactory.cs
@@ -30,7 +30,7 @@ IBrowserPageFactory browserPageFactory
public async ValueTask> CreateBrowser()
{
var browserOptions = options.Value.BrowserOptions;
-
+ var globalJavascriptSettings = options.Value.GlobalJavascriptSettings;
var browserExecutableLocation = browserOptions.BrowserExecutableLocation is not null
? browserOptions.BrowserExecutableLocation.FullName
: BrowserFinder.Find(browserOptions.Browser);
@@ -93,6 +93,7 @@ public async ValueTask> CreateBrowser()
devToolsActivePortDirectory,
connection,
browserOptions,
+ globalJavascriptSettings,
browserLogger,
browserPageFactory
);
diff --git a/src/BlazorReports/Services/BrowserServices/IBrowserService.cs b/src/BlazorReports/Services/BrowserServices/IBrowserService.cs
index 6ec10f6..ddf19b2 100644
--- a/src/BlazorReports/Services/BrowserServices/IBrowserService.cs
+++ b/src/BlazorReports/Services/BrowserServices/IBrowserService.cs
@@ -17,14 +17,22 @@ public interface IBrowserService
/// The pipe writer to write the report to
/// The HTML to use in the report
/// The page settings to use in the report
+ /// The current report javascript settings
/// The cancellation token
/// The result of the operation
ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
string html,
BlazorReportsPageSettings pageSettings,
+ BlazorReportCurrentReportJavascriptSettings currentReportJavascriptSettings,
CancellationToken cancellationToken
);
}
diff --git a/src/BlazorReports/Services/BrowserServices/Problems/CompletedSignalTimeoutProblem.cs b/src/BlazorReports/Services/BrowserServices/Problems/CompletedSignalTimeoutProblem.cs
new file mode 100644
index 0000000..e2e68e0
--- /dev/null
+++ b/src/BlazorReports/Services/BrowserServices/Problems/CompletedSignalTimeoutProblem.cs
@@ -0,0 +1,6 @@
+namespace BlazorReports.Services.BrowserServices.Problems;
+
+///
+/// Represents a problem where the javascript has timed out
+///
+public readonly record struct CompletedSignalTimeoutProblem;
diff --git a/src/BlazorReports/Services/BrowserServices/Responses/RuntimeEvaluateResponse.cs b/src/BlazorReports/Services/BrowserServices/Responses/RuntimeEvaluateResponse.cs
new file mode 100644
index 0000000..f6663dc
--- /dev/null
+++ b/src/BlazorReports/Services/BrowserServices/Responses/RuntimeEvaluateResponse.cs
@@ -0,0 +1,44 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace BlazorReports.Services.BrowserServices.Responses;
+
+///
+/// Response returned from the "Runtime.evaluate" DevTools command
+///
+/// The evaluation result object if the command succeeded
+/// Details about any exception thrown in JS
+/// Whether an exception was thrown
+public sealed record RuntimeEvaluateResponse(
+ [property: JsonPropertyName("result")] RuntimeEvaluateResult? Result,
+ [property: JsonPropertyName("exceptionDetails")]
+ RuntimeEvaluateExceptionDetails? ExceptionDetails,
+ [property: JsonPropertyName("wasThrown")] bool WasThrown
+);
+
+///
+/// Contains the result of a "Runtime.evaluate" call
+///
+/// The type of the result (e.g., "string", "object", "undefined")
+/// The raw JSON value (if returnByValue was used)
+/// A textual description (especially for objects/functions)
+public sealed record RuntimeEvaluateResult(
+ [property: JsonPropertyName("type")] string? Type,
+ [property: JsonPropertyName("value")] JsonElement? Value,
+ [property: JsonPropertyName("description")] string? Description
+);
+
+///
+/// Contains exception details if JS threw an error
+///
+/// The error message text
+public sealed record RuntimeEvaluateExceptionDetails(
+ [property: JsonPropertyName("text")] string? Text
+);
+
+///
+/// JSON serialization context for the "Runtime.evaluate" DevTools command
+///
+[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
+[JsonSerializable(typeof(BrowserResultResponse))]
+internal sealed partial class RuntimeEvaluateResponseSerializationContext : JsonSerializerContext;
diff --git a/src/BlazorReports/Services/IReportService.cs b/src/BlazorReports/Services/IReportService.cs
index 3f8d66f..d1f5c78 100644
--- a/src/BlazorReports/Services/IReportService.cs
+++ b/src/BlazorReports/Services/IReportService.cs
@@ -17,14 +17,22 @@ public interface IReportService
///
/// The pipe writer to write the report to
/// The data to use in the report
+ /// The report to generate
/// The cancellation token
/// The component to use in the report
/// The type of data to use in the report
/// The generated report
ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
+ BlazorReport report,
TD data,
CancellationToken cancellationToken = default
)
@@ -41,7 +49,13 @@ public interface IReportService
/// The type of data to use in the report
/// The generated report
ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
BlazorReport blazorReport,
@@ -58,7 +72,13 @@ public interface IReportService
/// The cancellation token
/// The generated report
ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
BlazorReport blazorReport,
diff --git a/src/BlazorReports/Services/JavascriptServices/JavascriptContainer.cs b/src/BlazorReports/Services/JavascriptServices/JavascriptContainer.cs
new file mode 100644
index 0000000..976f2b0
--- /dev/null
+++ b/src/BlazorReports/Services/JavascriptServices/JavascriptContainer.cs
@@ -0,0 +1,44 @@
+using System.Text;
+using Microsoft.AspNetCore.Hosting;
+
+namespace BlazorReports.Services.JavascriptServices;
+
+internal sealed class JavascriptContainer
+{
+ ///
+ /// A dictionary of (FileName -> FileContent) for all .js files.
+ ///
+ public Dictionary Scripts { get; } = new();
+
+ ///
+ /// Scans the wwwroot/js folder (or any folder you specify) and reads
+ /// all .js file contents into memory.
+ ///
+ public JavascriptContainer(IWebHostEnvironment env)
+ {
+ // 1. Get the physical path to the wwwroot folder
+ var webRootPath = env.WebRootPath;
+
+ // 2. Build path to the "js" subfolder
+ var jsDir = Path.Combine(webRootPath, "js");
+
+ if (Directory.Exists(jsDir))
+ {
+ // 3. Get all *.js files recursively
+ var allJsFiles = Directory.GetFiles(jsDir, "*.js", SearchOption.AllDirectories);
+
+ foreach (var filePath in allJsFiles)
+ {
+ // For example: "C:\MyApp\wwwroot\js\somefolder\file.js"
+ // We'll use the file name (e.g. "file.js") or a relative path as key
+ var fileName = Path.GetFileName(filePath);
+
+ // 4. Read the file text content
+ var content = File.ReadAllText(filePath, Encoding.UTF8);
+
+ // 5. Store in the dictionary
+ Scripts[fileName] = content;
+ }
+ }
+ }
+}
diff --git a/src/BlazorReports/Services/ReportService.cs b/src/BlazorReports/Services/ReportService.cs
index 6ba3aae..40dfb0e 100644
--- a/src/BlazorReports/Services/ReportService.cs
+++ b/src/BlazorReports/Services/ReportService.cs
@@ -3,10 +3,12 @@
using BlazorReports.Models;
using BlazorReports.Services.BrowserServices;
using BlazorReports.Services.BrowserServices.Problems;
+using BlazorReports.Services.JavascriptServices;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using OneOf;
using OneOf.Types;
@@ -31,15 +33,23 @@ IBrowserService browserService
/// Generates a report using the specified component and data
///
/// The pipe writer to write the report to
+ /// The report to generate
/// The data to use in the report
/// The cancellation token
/// The component to use in the report
/// The type of data to use in the report
/// The generated report
public async ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
+ BlazorReport report,
TD data,
CancellationToken cancellationToken = default
)
@@ -74,6 +84,7 @@ public async ValueTask<
pipeWriter,
html,
reportRegistry.DefaultPageSettings,
+ report.CurrentReportJavascriptSettings,
cancellationToken
);
}
@@ -88,7 +99,13 @@ public async ValueTask<
/// The type of data to use in the report
/// The generated report
public async ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
BlazorReport blazorReport,
@@ -99,6 +116,9 @@ public async ValueTask<
{
using var scope = serviceProvider.CreateScope();
var loggerFactory = scope.ServiceProvider.GetRequiredService();
+ var javascriptContainer = scope.ServiceProvider.GetRequiredService();
+ var options = scope.ServiceProvider.GetRequiredService>();
+ var javascriptInternalSettings = options.Value.GlobalJavascriptSettings;
var baseStyles = string.Empty;
if (!string.IsNullOrEmpty(blazorReport.BaseStyles))
@@ -134,6 +154,25 @@ public async ValueTask<
{
baseComponentParameters.Add("BaseStyles", baseStyles);
}
+ if (javascriptContainer.Scripts.Count > 0)
+ {
+ baseComponentParameters.Add("Scripts", javascriptContainer.Scripts);
+ }
+ //Checks if the report has a custom signal if not use the global one
+ if (blazorReport.CurrentReportJavascriptSettings.ReportIsReadySignal is not null)
+ {
+ baseComponentParameters.Add(
+ "ReportIsReadySignal",
+ blazorReport.CurrentReportJavascriptSettings.ReportIsReadySignal
+ );
+ }
+ else
+ {
+ baseComponentParameters.Add(
+ "ReportIsReadySignal",
+ javascriptInternalSettings.ReportIsReadySignal
+ );
+ }
baseComponentParameters.Add("ChildComponentType", blazorReport.Component);
baseComponentParameters.Add("ChildComponentParameters", childComponentParameters);
@@ -151,7 +190,13 @@ public async ValueTask<
var pageSettings = blazorReport.PageSettings ?? reportRegistry.DefaultPageSettings;
- return await browserService.GenerateReport(pipeWriter, html, pageSettings, cancellationToken);
+ return await browserService.GenerateReport(
+ pipeWriter,
+ html,
+ pageSettings,
+ blazorReport.CurrentReportJavascriptSettings,
+ cancellationToken
+ );
}
else
{
@@ -175,7 +220,13 @@ public async ValueTask<
/// The cancellation token
/// The generated report
public ValueTask<
- OneOf
+ OneOf<
+ Success,
+ ServerBusyProblem,
+ OperationCancelledProblem,
+ BrowserProblem,
+ CompletedSignalTimeoutProblem
+ >
> GenerateReport(
PipeWriter pipeWriter,
BlazorReport blazorReport,