Giter Club home page Giter Club logo

spawndev.blazorjs's Introduction

Blazor C# C++ CSS3 HTML5 JavaScript TypeScript Markdown PHP SQLite MySQL Gimp Gnu Image Manipulation Program Inkscape Blender OpenCV NodeJS jQuery Drupal HAProxy OpenGL DirectX WebGL NPM AWS Github Pages Visual Studio Code Visual Studio Apache Git GitHub Windows Linux Android Google Chrome Firefox Edge 3D Lenticular Displays

spawndev.blazorjs's People

Contributors

lostbeard avatar ronpeters avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

spawndev.blazorjs's Issues

Task Cancellation

Hello,

First of all, thank you for making such an awesome library. I was disappointed by the lack of .NET 7 support in BlazorWorker, so I'm glad to have stumbled upon yours.

I am trying to utilize web workers to execute some long running tasks (running dynamically compiled code / unit tests), and some of them may run infinitely long. Which is why I am searching for some way to terminate tasks after a certain period of time.

Using BlazorJS.WebWorkers I was able to offload the compilation and execution of the tests to a WebWorker, so the browser UI no longer hangs. And by chaining WaitAsync to the Task that is returned by the WebWorker and awaiting that, it will throw a TimeOutException when the Task exceeds the timeout that has been set. All good so far!

However, the WebWorker continues executing the Task until completion, and I'd like for a way to stop it from doing that..
I do not control all the inner workings of my test runner, so I am unable to e.g. send over a CancellationToken and check that continuously. Do you have any idea how to achieve this?

Here's a code snippet for reference:

public async Task CompileAndRun()
{
    var code = await Editor.GetValue();
    
    _webWorker ??= await WebWorkerService.GetWebWorker();
    
    var runner = _webWorker.GetService<ITestWorker>();

    try
    {
        var result = await runner
            .RunTests(code)
            .WaitAsync(TimeSpan.FromSeconds(5));

        if (result.TestCount == 0)
        {
            await Alert("An unexpected error occurred");
            return;
        }

        if (result.FailedCount > 0)
        {
            await Alert("One or more tests failed");
        }
        else
        {
            await Alert("All tests passed, nice!");
        }
    }
    catch (TimeoutException e)
    {
        await Alert("Test suite timed out");
    }
    finally
    {
        _webWorker?.Dispose();
        _webWorker = null;
        // In case of timeout, WebWorker is still doing work at this point..
    }
}

StackOverflowException at uint.TryConvertFromTruncating<uint16> (uint16,uint&)

Describe the bug
When trying to run a method in a service from a worker, the following debug messages and error comes up, the method does not run.

Got WebWorker instance Id 840b320e-8d30-4bb1-8998-3288312843a7
blazor.webassembly.js:1 Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id 840b320e-8d30-4bb1-8998-3288312843a7
blazor.webassembly.js:1  [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow.
Dt @ blazor.webassembly.js:1
blazor.webassembly.js:1  at uint.TryConvertFromTruncating<uint16> (uint16,uint&) [0x0016b] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at uint.CreateTruncating<uint16> (uint16) [0x0002c] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (uint16*,uint16*,uintptr) [0x00318] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (System.ReadOnlySpan`1<uint16>,System.Span`1<uint16>,int&) [0x00062] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Text.Ascii.ToLower (System.ReadOnlySpan`1<char>,System.Span`1<char>,int&) [0x0000d] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (System.ReadOnlySpan`1<char>,System.Span`1<char>) [0x00035] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (string) [0x00119] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Globalization.TextInfo.ToLower (string) [0x0001b] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at string.ToLowerInvariant () [0x00006] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetPropertyJSName (System.Reflection.PropertyInfo) [0x00007] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:54
Dt @ blazor.webassembly.js:1
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00338] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:177
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00350] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:179
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453

The last few lines are repeated many times, maybe a few 100's (see screenshot), then:

blazor.webassembly.js:1 
 (null)
blazor.webassembly.js:1 
 (null)
blazor.webassembly.js:1 
 program exited (with status: 1), but keepRuntimeAlive() is set (counter=1) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)
SpawnDev.BlazorJS.lib.module.js:305 
 Callback invokeMethod error: 
(2) ['Invoke', {…}]
 null 
ExitStatus {name: 'ExitStatus', message: 'Program terminated with exit(1)', status: 1}
blazor.webassembly.js:1 
 (null)
blazor.webassembly.js:1 
 Unhandled Exception:
blazor.webassembly.js:1 
 StackOverflowException
blazor.webassembly.js:1 
 [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow.
blazor.webassembly.js:1 
 at Microsoft.AspNetCore.Components.Reflection.PropertySetter.CallPropertySetter<MudBlazor.MudAppBar, int> (System.Action`2<MudBlazor.MudAppBar, int>,object,object) [0x0002b] in <b6ecada0543848beb43c97c645b55df3>:0
blazor.webassembly.js:1 
 at Microsoft.AspNetCore.Components.Reflection.PropertySetter.SetValue (object,object) [0x00008] in <b6ecada0543848beb43c97c645b55df3>:0

The error happens in this method (note the debug messages in the error):

public async Task<List<object>> GetToListAsync(Type entityType, CancellationToken cancellationToken = default, bool doSync = true)
{
    if (doSync)
    {
        Console.WriteLine("Getting ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>()");
        var webWorker = await _webWorkerService.GetWebWorker();
        Console.WriteLine($"Got WebWorker instance Id {webWorker?.LocalInfo.InstanceId}");
        var service = webWorker.GetService<ISyncService>();
        Console.WriteLine($"Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id {_webWorkerService.InstanceId}");
        await service.SyncRemoteWithLocal(entityType, cancellationToken);
        Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.TaskPool.GetService<ISyncService>()");
    }
    return await _unitOfWork.GetListAsync(entityType,cancellationToken);
}

The first "Console.WriteLine" is never show in the browser's console:

public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0)
{
    Console.WriteLine($"SyncService.SyncRemoteWithLocal({entityType.Name}) started");

    _listenerService.TriggerNotifySyncStarted(entityType);

    var localEntitiesSet = _unitOfWork.Get(entityType);
    var localEntities = await localEntitiesSet.Cast<object>().ToListAsync(cancellationToken);

    var remoteCount = await _httpClient.GetCount(entityType, cancellationToken);
    _listenerService.TriggerSyncProgress(entityType, 0, remoteCount / pageSize);

    IEnumerable<object> remoteEntities;
    if (pageNumber == 0)
    {
        //do a sliding window sync
        for (int i = 1; i <= (remoteCount / pageSize)+1; i++)
        {
            _listenerService.TriggerSyncProgress(entityType, i, remoteCount / pageSize);
            remoteEntities = await _httpClient.GetPage(entityType, i, pageSize, cancellationToken);
            await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);

            if (cancellationToken.IsCancellationRequested)
            {
                break;
            }

            _listenerService.TriggerSyncComplete(entityType);
            //if (i == 1)
            //{
            //    _listenerService.TriggerSyncComplete(entityType); //show first page of sync as complete
            //}
        }
    }
    else if (pageNumber > 0)
    {
        remoteEntities = await _httpClient.GetPage(entityType, pageNumber, pageSize, cancellationToken);
        await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
    }

    _listenerService.TriggerNotifySyncCompleted(entityType);
    _listenerService.TriggerSyncComplete(entityType);
}

To Reproduce

  1. Start with .Net 7 Blazor (WASM + ASP.Net Core Hosted) app.
  2. Upgrade to .Net 8
  3. Add several libraries including Besql
  4. Add a scoped service in Program.cs via its interface.
  5. Get the scoped service via web workers.
  6. Run a method in the scoped service.

Expected behavior
The method to run.

Screenshots
image

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser: MS Edge
  • Version: 124.0.2478.97 (Official build) (64-bit)

Additional context
Add any other context about the problem here.

HttpClient not working in WebWorkers with .Net 6

Is System.Net.Http.HttpClient supported in this webworker?

I'm using .net 6.0 and am getting this exception when calling SendAsync from HttpClient.

SyncDataException: Data Sync Error
VM8:3 ---> System.Net.Http.HttpRequestException: TypeError: Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation
VM8:3 ---> System.Runtime.InteropServices.JavaScript.JSException: TypeError: Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation
VM8:3 at System.Net.Http.BrowserHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
VM8:3 --- End of inner exception stack trace ---
VM8:3 at System.Net.Http.BrowserHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
VM8:3 at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)

Add IDBIndex [IndexedDB]

There does not seem to be a way to access the indexes in IndexedDB. In JavaScript, you would do something like this:

const myIndex = objectStore.index("myIndexedField"); // index name
const getRequest = myIndex.get(2); // index value to match

Would it be possible to add this interface? Probably something more like objectStore.GetIndex(indexName)

https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex

Also IDBKeyRange and IDBCursor would be awesome and would solve my use cases.

TypeError: /src="(.+?)"/.exec(...) is null

I'm trying to implement the webworkers in .NET 8 but I'm getting the following error in spawndev.blazorjs.webworkers.js:119:47

TypeError: /src="(.+?)"/.exec(...) is null

The specific line where this error occurs is let scriptSrc = /src="(.+?)"/.exec(scriptTagBody)[1];

The browser I'm testing this is in Firefox on Windows 11. The error occurs when running await workerService.GetWebWorker() in Blazor WASM

Wrong event subscribed in callback for OnAppInstalled

I believe the wrong event is subscribed to on this line of code:

public JSEventCallback OnAppInstalled { get => new JSEventCallback(o => AddEventListener("afterprint", o), o => RemoveEventListener("afterprint", o)); set { /** set MUST BE HERE TO ENABLE += -= operands **/ } }

public JSEventCallback OnAppInstalled { get => new JSEventCallback(o => AddEventListener("afterprint", o), o => RemoveEventListener("afterprint", o)); set { /** set MUST BE HERE TO ENABLE += -= operands **/ } }

Should be:
public JSEventCallback OnAppInstalled { get => new JSEventCallback(o => AddEventListener("appinstalled", o), o => RemoveEventListener("appinstalled", o)); set { /** set MUST BE HERE TO ENABLE += -= operands **/ } }

Incompatiblity with Microsoft.Authentication.WebAssembly.Msal Package when using WebWorkers

First of all, thank you for all the effort you have put into this library :)

Issue:

If the project uses Microsoft.Authentication.WebAssembly.Msal nuget package, an exception will be thrown at this line:

var webWorker = await workerService.GetWebWorker();

Exception Detail

image

Root Cause

The root cause was MSAL library was trying to read appsettings.json which is not available at the location specified.

image

Workaround

Create appsettings.json file at wwwroot/_content/SpawnDev.BlazorJS.WebWorkers/appsettings.json with empty JSON object as follows:

image

with workaround, the library functions normally.

image

Call to WebWorker service method with multiple signatures not working as expected

Could you update the documentation for blazor worker to show that each function names must be unique with unique signatures.
Even though the compiler allows same function names, with different signatures, your plugin does not due to (presumably) limitations set by the way things are serialized.

An example that I ran into that appears to fail is...

//FAIL
public interface IContractService
{
public Task<byte[]> DoThis(string value);
public Task<byte[]> DoThis(double value);
}

//SUCCESS
public interface IContractService
{
public Task<byte[]> DoThis(string value);
public Task<byte[]> DoThat(double value);
}

console log of error:

ERROR: MessageEvent {isTrusted: true, data: {…}, origin: '', lastEventId: '', source: null, …}
VM8:3 ERROR: Exception has been thrown by the target of an invocation.
VM8:3 ERROR stacktrace: at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
VM8:3 at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
VM8:3 at SpawnDev.BlazorJS.IJSInProcessRuntimeExtensions.Invoke(IJSInProcessRuntime _js, Type returnType, String identifier, Object[] args)
VM8:3 at SpawnDev.BlazorJS.JSInterop.Get(Type returnType, IJSInProcessObjectReference targetObject, Int32 identifier)
VM8:3 at SpawnDev.BlazorJS.IJSInProcessObjectReferenceExtensions.Get(IJSInProcessObjectReference targetObject, Type returnType, Int32 identifier)
VM8:3 at SpawnDev.BlazorJS.WebWorkers.ServiceCallDispatcher.PostDeserializeArgs(String requestId, MethodInfo methodInfo, Int32 argsLength, Func`3 getArg)
VM8:3 at SpawnDev.BlazorJS.WebWorkers.ServiceCallDispatcher._worker_OnMessage(MessageEvent e)

Getting ImportShimBlazorWASM.Execute warning on build/publish

I get the following warning after build/publish:

##[warning]/home/vsts/.nuget/packages/spawndev.blazorjs.webworkers/2.2.56/build/SpawnDev.BlazorJS.WebWorkers.targets(40,3): Warning : ********************************** ImportShimBlazorWASM.Execute **********************************

Do I have something misconfigured? Or should it be ignored, and if so, is there a way to suppress this? I don't want it to litter my logs if it's not an actual problem.

Thanks!

unusual console messages when first loading a web page with webworkers only configured in Program.cs

Describe the bug
Blazor WASM resources appear as if loaded multiple times, as shown in the browser console, it previously only appears once:

dotnet Loaded 17.94 MB resourcesThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller if you install wasm-tools workload. See also https://aka.ms/dotnet-wasm-features

Also, the current error appears when the page is first loaded, also twice:

blazor.webassembly.js:1  Error: One or more errors occurred. (Could not find 'synchronizeDbWithCache' ('synchronizeDbWithCache' was undefined).
Error: Could not find 'synchronizeDbWithCache' ('synchronizeDbWithCache' was undefined).
    at http://localhost/_framework/blazor.webassembly.js:1:368
    at Array.forEach (<anonymous>)
    at l.findFunction (http://localhost/_framework/blazor.webassembly.js:1:336)
    at w (http://localhost/_framework/blazor.webassembly.js:1:5079)
    at http://localhost/_framework/blazor.webassembly.js:1:2872
    at new Promise (<anonymous>)
    at b.beginInvokeJSFromDotNet (http://localhost/_framework/blazor.webassembly.js:1:2835)
    at Object.vn [as invokeJSJson] (http://localhost/_framework/blazor.webassembly.js:1:58849)
    at http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:178364
    at Tl (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:179198))
    at Jn (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:31614)
    at Tl (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:182415)
    at do_icall (wasm://wasm/03f7fb96:wasm-function[2658]:0xd6e5a)
    at do_icall_wrapper (wasm://wasm/03f7fb96:wasm-function[2543]:0xd1dce)
    at mono_interp_exec_method (wasm://wasm/03f7fb96:wasm-function[2536]:0xc3cfc)
    at interp_entry (wasm://wasm/03f7fb96:wasm-function[2612]:0xd51f2)
    at interp_entry_static_0 (wasm://wasm/03f7fb96:wasm-function[2634]:0xd5cb6)
    at wasm_native_to_interp_System_Private_CoreLib_ThreadPool_BackgroundJobHandler (wasm://wasm/03f7fb96:wasm-function[16013]:0x35ed45)
    at mono_background_exec (wasm://wasm/03f7fb96:wasm-function[3547]:0x1074b9)
    at e.<computed> (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:215551)

Other than those messages, the page actually loads and functions as normal.

To Reproduce

  • Started with a Blazor .Net 7 (WASM+ASP.NET Core) hosted app.
  • Upgraded app to .Net 8.
  • Followed the steps in README.md to add SpawnDev.BlazorJS.WebWorkers in program.cs

Expected behavior

  • No duplicate information messages.
  • No error messages.

Screenshots
image

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser: MS Edge
  • Version: MS Edge 124.0.2478.97 (Official build) (64-bit)

StackOverflowException when calling my async method

My Blazor WASM app is running on net7.0, and using this package

<PackageVersion Include="SpawnDev.BlazorJS.WebWorkers" Version="2.2.0" />

I'm getting the below exception when calling the following async method from within my ViewModel

public async Task DownloadFile(Product product)
{
    using var _ = Busy();
    using var webWorker = await _webWorkerService.GetWebWorker() ?? throw new InvalidOperationException("Unable to find web worker");
    var exportService = webWorker.GetService<IProductExportService>();
    var result = await exportService.Export(product).ConfigureAwait(false);
    await _jsRuntime.InvokeVoidAsync("BlazorDownloadFile", result.Name, Convert.ToBase64String(result.Data)).ConfigureAwait(true);
}
image

I think I have my Program.cs setup correctly, so I'm at a loss as to what to do.

image

Wrong _frameworkPath when the location includes the index doc name

If you include the trailing /index.html in the browser address (I have to do this in certain situations), the _framework and _content paths are no longer correct (they include the /index.html).

I guess this would need to be checked in spawndev.blazorjs.webworkers.js near line 60.

Question: How to cancel WebWorkerService.TaskPool.Run()?

I'm doing this and need to cancel it sometimes:

await WebWorkerService.TaskPool.Run(() => AssembleSync(request));

AssembleSync is:

public byte[]? AssembleSync(CorePDFAssembleRequest request)
{
    // ...
}

Can that be cancelled when using the oh-so-convenient TaskPool-style interface?

Alternately I tried

Func<bool> checkCancelled = () => doTheCheck();
await WebWorkerService.TaskPool.Run(() => AssembleSync1(request, checkCancelled));

AssembleSync1 is:

public byte[]? AssembleSync1(CorePDFAssembleRequest request, Func<bool> checkCancelled)
{
    // ...
}

but unfortunately I get a runtime exception

blazor.webassembly.js:1 Uncaught (in promise) Error: System.Exception: Object of type 'System.Action1[System.Boolean]' cannot be converted to type 'System.Func1[System.Boolean]'.`

Another alternative would be to send the WebWorker a cancel message. Is that possible usingTaskPool-style?

Any wisdom is appreciated.

BeforeInstallPromptEvent.Prompt throws exception

I'm trying to follow best practice to create a custom PWA install experience: https://web.dev/customize-install/

In short, this involves hooking the BeforeInstallPrompt event, caching the BeforeInstallPromptEvent, and then calling it later after presenting a custom UI.

I do something like this:

protected override async Task OnInitializedAsync()
{
    using var window = JS.Get<Window>("window");
    window.OnBeforeInstallPrompt += Window_OnBeforeInstallPrompt;
}

private BeforeInstallPromptEvent? DeferredPrompt;
void Window_OnBeforeInstallPrompt(BeforeInstallPromptEvent e)
{
    e.PreventDefault();
    DeferredPrompt = e;
    InvokeAsync(() => StateHasChanged()); // This enables the custom install button in the UI
}

// This gets called with the result of the custom dialog we present to the user
async Task HandleInstallDialog(DialogResult dialogResult)
{
    try
    {
        if (dialogResult.Cancelled)
        {
            Logger.LogInformation("User cancelled install app promo dialog");
        }
        else if (DeferredPrompt != null)
        {
            var installResult = await DeferredPrompt.Prompt; // <-- crashes here
            Logger.LogInformation($"User responded to app installation with outcome: {installResult.Outcome}");
        }
    }
    catch (Exception ex)
    {
        Logger.LogError("Error installing app", ex);
    }
}

Exception is a JSException with Message: 'undefined\nundefined'

...btw, I think Prompt should really be a function Prompt()

It's kind of a pain to test because Chrome disables the beforeInstallPrompt event after the crash, so if you need to force it, go to Dev Tools and enter this into the Console: window.dispatchEvent(new Event('beforeinstallprompt'))

IJSRuntime

I have references I need to use that rely on IJSRuntime in the scope. Code that relies on IJSRuntime seems to fail and/or work in a different sand box than the rest of the website. Any insight into this or workarounds wasm apps?

Example would be using Blazored.LocalStorage within a SpawnDev Web Worker. When I write to storage it will be wiped with the service, after the service is disposed.

WebWorker work is unclear, console messages stops, debug is unreachable

Describe the bug
Hello!
In Blazor WebAssembly
I create a webWorker to processAudioData:

                var webWorker = await _workerService.GetWebWorker();
                _proccessingService = webWorker.GetService<IProcessingService>();

then i call it:
var result = await _proccessingService.ProcessData(samples);
For test - i hardcoded result but i always get the result of the parameterless constructor of the object response type.
And console stops write any debug messages.

Here is Program.cs file:

builder.Services.AddBlazorJSRuntime();
builder.Services.AddWebWorkerService();
builder.Services.AddSingleton<IProcessingService, AudioProcessingService>();
await builder.Build().BlazorJSRunAsync();

**Desktop **

  • OS: Windows
  • Browser: Chrome 118.0.5993.118 (Official Build) (64-bit)

Keyboard events are missing

I would like to be able to attach event handlers to window.keydown and window.keyup events and receive a typed KeyboardEvent

[Feature request] Add possibility to configure System.Text.Json

My simple F# demo project is working, but I get stuck if I use F# types that are not supported out of the box by System.Text.Json. I would need to add a custom F# converter such as Fsharp.SystemTextJson.

Would you consider adding a way to do that?

E.g. just making the getter of RuntimeJsonSerializerOptions public; or add a service AddJsonOptions like the MVC controllers do (see here and here).

Could not find 'blazorCulture.get' ('blazorCulture' was undefined).

I encounter this error on .NET 8 RC-1

Error: Could not find 'blazorCulture.get' ('blazorCulture' was undefined).

Program.cs

services.AddBlazorJSRuntime();
services.AddWebWorkerService();
services.AddSingleton<INotificationWorker, NotificationWorker>();

On Razor

    protected override async Task OnInitializedAsync()
    {
        var webWorker = await worker.GetWebWorker();
        webWorker!.OnMessage += (sender, msg) =>
        {
            Log("Hello world", LogType.Error);
        };

        var service = webWorker!.GetService<INotificationWorker>();
        await service!.DoAsync();
    }

The interface

public interface INotificationWorker
{
    Task DoAsync();
}

public class NotificationWorker : INotificationWorker
{
    public async Task DoAsync()
    {
        await Task.CompletedTask;
    }
}

On the project

    <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.2.10" />

cannot resolve service.. which has another service injected into it

Describe the bug
Getting the following error when calling a service method using a web worker.

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Cannot resolve scoped service 'RihalDotnetTemplateWasm.Client.Data.UnitOfWork' from root provider.
System.Exception: Cannot resolve scoped service 'RihalDotnetTemplateWasm.Client.Data.UnitOfWork' from root provider.
   at SpawnDev.BlazorJS.WebWorkers.ServiceCallDispatcher.Call(MethodInfo methodInfo, Object[] args) in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\ServiceCallDispatcher.cs:line 372
   at SpawnDev.BlazorJS.Reflection.InterfaceCallDispatcher`1.<InvokeTaskVoid>d__10[[RihalDotnetTemplateWasm.Client.Services.ISyncService, RihalDotnetTemplateWasm.Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS\Reflection\InterfaceCallDispatcher.cs:line 42
   at RihalDotnetTemplateWasm.Client.Data.DynamicRepository.GetToListAsync(Type entityType, CancellationToken cancellationToken, Boolean doSync) in C:\Users\alhar\source\repos\rihal-dotnet-template-wasm\RihalDotnetTemplateWasm\Client\Data\DynamicRepository.cs:line 36
   at RihalDotnetTemplateWasm.Client.Features.Models.ModelsTable.OnParametersSetAsync() in C:\Users\alhar\source\repos\rihal-dotnet-template-wasm\RihalDotnetTemplateWasm\Client\Features\Models\ModelsTable.razor:line 72
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

To Reproduce
Steps to reproduce the behaviour:

  1. Created a service with an interface to use in a web worker.
  2. Injected a service (not using an interface) into the service in step 1.
  3. Called a method from service in step 1 using a web worker.
  4. Maybe all services in play must be using an interface?

Expected behavior
The method to run

Screenshots
N/A

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser: MS Edge
  • Version: 124.0.2478.97 (Official build) (64-bit)

Additional context
DynamicRepository.cs

public async Task<List<object>> GetToListAsync(Type entityType, CancellationToken cancellationToken = default, bool doSync = true)
{
    if (doSync)
    {
        //_listenerService.TriggerSyncStart(entityType);
        //await _syncService.SyncRemoteWithLocal(entityType, cancellationToken);
        Console.WriteLine("Getting ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>()");
        var webWorker = await _webWorkerService.GetWebWorker();
        Console.WriteLine($"Got WebWorker instance Id {webWorker?.LocalInfo.InstanceId}");
        var service = webWorker.GetService<ISyncService>();
        Console.WriteLine($"Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id {_webWorkerService.InstanceId}");
        await service.SyncRemoteWithLocal(entityType, cancellationToken);
        Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.TaskPool.GetService<ISyncService>()");

        //Console.WriteLine("_webWorkerService.WindowTask.Run(() => _syncService.SyncRemoteWithLocal(entityType, cancellationToken, 0));");
        //await _webWorkerService.WindowTask.Run(() => _syncService.SyncRemoteWithLocal(entityType, cancellationToken, 0));
        //Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.WindowTask.Run(() => _syncService.SyncRemoteWithLocal(entityType, cancellationToken, 0));");
    }
    return await _unitOfWork.GetListAsync(entityType,cancellationToken);
}

SyncService.cs

public class SyncService : ISyncService
{
    private readonly GenericHttpClient _httpClient;
    private readonly ISyncListenerService _listenerService;
    private readonly UnitOfWork _unitOfWork;

    private const int pageSize = 30;

    public SyncService(GenericHttpClient httpClient, UnitOfWork unitOfWork, ISyncListenerService listenerService)
    {
        _httpClient = httpClient;
        _unitOfWork = unitOfWork;
        _listenerService = listenerService;

        _listenerService.RegisterListener(this);

        _listenerService.SyncStart += async (sender, entityType) =>
        {
            await SyncRemoteWithLocal(entityType);
        };

        _listenerService.SyncItemById += async (sender, args) =>
        {
            await SyncRemoteWithLocal(args.entityType, args.id);
        };
    }

    public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0)
    {
        Console.WriteLine($"SyncService.SyncRemoteWithLocal({entityType.Name}) started");

        _listenerService.TriggerNotifySyncStarted(entityType);

        var localEntitiesSet = _unitOfWork.Get(entityType);
        var localEntities = await localEntitiesSet.Cast<object>().ToListAsync(cancellationToken);

        var remoteCount = await _httpClient.GetCount(entityType, cancellationToken);
        _listenerService.TriggerSyncProgress(entityType, 0, remoteCount / pageSize);

        IEnumerable<object> remoteEntities;
        if (pageNumber == 0)
        {
            //do a sliding window sync
            for (int i = 1; i <= (remoteCount / pageSize)+1; i++)
            {
                _listenerService.TriggerSyncProgress(entityType, i, remoteCount / pageSize);
                remoteEntities = await _httpClient.GetPage(entityType, i, pageSize, cancellationToken);
                await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);

                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                _listenerService.TriggerSyncComplete(entityType);
                //if (i == 1)
                //{
                //    _listenerService.TriggerSyncComplete(entityType); //show first page of sync as complete
                //}
            }
        }
        else if (pageNumber > 0)
        {
            remoteEntities = await _httpClient.GetPage(entityType, pageNumber, pageSize, cancellationToken);
            await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
        }

        _listenerService.TriggerNotifySyncCompleted(entityType);
        _listenerService.TriggerSyncComplete(entityType);
    }
}

UnitOfWork.cs

public class UnitOfWork : IDisposable
{
    private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
    private readonly IServiceProvider _serviceProvider;
    private AppDbContext _dbContext;

    public UnitOfWork(IDbContextFactory<AppDbContext> dbContextFactory, IServiceProvider serviceProvider)
    {
        _dbContextFactory = dbContextFactory;
        _serviceProvider = serviceProvider;
    }
    //More methods below
}

Program.cs

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.EntityFrameworkCore;
using MudBlazor;
using MudBlazor.Services;
using RihalDotnetTemplateWasm.Client;
using RihalDotnetTemplateWasm.Client.Data;
using RihalDotnetTemplateWasm.Client.Services;
using System.Text.Json;
using System.Text.Json.Serialization;
using SpawnDev.BlazorJS;
using SpawnDev.BlazorJS.WebWorkers;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// Configure EF Core logging
builder.Logging.SetMinimumLevel(LogLevel.Warning); // Set default minimum log level to Warning
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning); // Filter out EF Core command logs
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Infrastructure", LogLevel.Warning); // Filter out EF Core infrastructure logs

builder.Services.AddBlazorJSRuntime();
builder.Services.AddWebWorkerService(webWorkerService =>
{
    // Optionally configure the WebWorkerService service before it is used
    // Default WebWorkerService.TaskPool settings: PoolSize = 0, MaxPoolSize = 1, AutoGrow = true
    // Below sets TaskPool max size to 2. By default the TaskPool size will grow as needed up to the max pool size.
    // Setting max pool size to -1 will set it to the value of navigator.hardwareConcurrency
    webWorkerService.TaskPool.MaxPoolSize = 2;
    // Below is telling the WebWorkerService TaskPool to set the initial size to 2 if running in a Window scope and 0 otherwise
    // This starts up 2 WebWorkers to handle TaskPool tasks as needed
    // Setting this to -1 will set the initial pool size to max pool size
    //webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 2 : 0;
    webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 2 : 0;
});
//builder.Services.RegisterServiceWorker<SyncServiceWorker>();

builder.Services.AddBesqlDbContextFactory<AppDbContext>(options =>
{
    options.UseSqlite("Data Source=app.db");
    options.UseSnakeCaseNamingConvention();
    options.EnableDetailedErrors();
    options.EnableSensitiveDataLogging(); // This enables logging parameter values
    options.LogTo(Console.WriteLine, LogLevel.Error); // Add logging to console
});
builder.Services.AddScoped<UnitOfWork>();
builder.Services.AddScoped<DynamicRepository>();

// Configure JSON serialization options
builder.Services.AddOptions<JsonSerializerOptions>().Configure(options =>
{
    options.ReferenceHandler = ReferenceHandler.Preserve; // Use ReferenceHandler.Preserve to handle object cycles
});

builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddSingleton<GenericHttpClient>();

//MudBlazor
builder.Services.AddMudServices(config =>
{
    config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
});
builder.Services.AddSingleton<MudThemeService>();

//Listeners to trigger UI updates from background services
builder.Services.AddSingleton<ISyncListenerService, SyncListenerService>();
builder.Services.AddScoped<ISyncService, SyncService>();

var app = builder.Build();

await using (var scope = app.Services.CreateAsyncScope())
{
    // Create db context
    await using var dbContext = await scope.ServiceProvider
        .GetRequiredService<IDbContextFactory<AppDbContext>>()
        .CreateDbContextAsync();

    // migrate database
    await dbContext.Database.MigrateAsync();
}

//await app.RunAsync();
await app.BlazorJSRunAsync();

How to subscribe to window.matchMedia changes

First of all, thank you for creating this project. I have gotten immediate use out of it.

I am trying to match this equivalent JavaScript using your API and can't figure it out. This code watches for changes in the color scheme and then calls my instance method "SystemThemeChanged":
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => { dotNetHelper.invokeMethodAsync("SystemThemeChanged"); });

The event listener is not being added to the window, but rather to the MediaQueryList returned from matchMedia. How do I accomplish this?

Webworkers gives error with msal

When I use the webworkers package with MSAL.

Then I get this error:
"There seems to be a problem. Try is later again!

Message:

Error: could not resolve endpoints. Please check network and try again. Detail: TypeError: Cannot read properties of null (reading 'filter')
"

One or more errors occurred. (An item with the same key has already been added. Key: Microsoft.Extensions.Http.IHttpMessageHandlerBuilderFilter)

In Blazor WASM PWA .net 8, my client's program.cs file is the following...

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection.Extensions;
using SpawnDev.BlazorJS;
using SpawnDev.BlazorJS.WebWorkers;

...

    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("#app");
    builder.RootComponents.Add<HeadOutlet>("head::after");

    // Add SpawnDev.BlazorJS.BlazorJSRuntime
    builder.Services.AddBlazorJSRuntime();

    // Add SpawnDev.BlazorJS.WebWorkers.WebWorkerService
    builder.Services.AddWebWorkerService();


    //PROBLEM EXISTS BECAUSE OF THIS LINE
    builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
    builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));

    builder.Services.AddMsalAuthentication(options =>
    {
        builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    });

    await builder.Build().BlazorJSRunAsync();

Expected behavior
Using a http message handler (BaseAddressAuthorizationMessageHandler) seems to cause this.
My authentication requires this and was wondering if there was some sort of work around or fix.

ERROR
ON STARTUP (in chrome dev console output)

Error: One or more errors occurred. (An item with the same key has already been added. Key: Microsoft.Extensions.Http.IHttpMessageHandlerBuilderFilter)
at Jn (marshal-to-js.ts:349:18)
at Ul (marshal-to-js.ts:306:28)
at 00b1e8b6:0x1faca
at 00b1e8b6:0x1bf8b
at 00b1e8b6:0xf172
at 00b1e8b6:0x1e7e4
at 00b1e8b6:0x1efda
at 00b1e8b6:0xcfec
at 00b1e8b6:0x440ad
at e. (cwraps.ts:338:24)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.