LostBeard / SpawnDev.BlazorJS

Full Blazor WebAssembly and Javascript interop. Supports all Javascript data types and web browser APIs.
https://blazorjs.spawndev.com
MIT License
111 stars 7 forks source link

BeforeInstallPromptEvent.Prompt throws exception #11

Closed RonPeters closed 1 year ago

RonPeters commented 1 year ago

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'))

RonPeters commented 1 year ago

I think there may be a discrepancy in Google's documentation. It says that both prompt() and userChoice return the install result, but the latest example shows a different flow: How to add Richer Install UI

installEvent.prompt();
const result = await installEvent.userChoice;

Perhaps Prompt is a void now?

LostBeard commented 1 year ago

There was a bug in the JSObject BeforeInstallPromptEvent. Fixed now in the repo. I'll update the Nuget shortly. Thank you for finding this issue. And yes, Prompt should have been a method.

BeforeInstallPromptEvent change:

 public class BeforeInstallPromptEvent : Event 
 {
  // ...
  // problem line
  public Task<InstallPromptResult> Prompt => JSRef.CallAsync<InstallPromptResult>("Prompt");
  // fixed
  public Task<InstallPromptResult> Prompt() => JSRef.CallAsync<InstallPromptResult>("prompt");
 }
LostBeard commented 1 year ago

One thing to note. While testing I noticed the beforeinstallprompt event usually fires before the Blazor app loads and therefore before we can attach Blazor event handlers. I ended up handling that by adding a bit of code into the index.html file to save the event and then pick it up in Blazor when it loads.

In a Githubissues.

  • Githubissues is a development platform for aggregating issues.