BlazorでWebPushを送ろう

# WebPushをBlazor WASMに送ろう
WebPushを送る案件に出会いました。WebPushに関しJavascriptでの文献は色々ありますが、Blazor + .NETでの実装は見かけることがありませんでしたので、JavascriptをBlazorに変換してみました。


## 環境
.NET8
Blazor WASM

## ソース

https://github.com/kyamamoto03/BlazorWebPush

上記のコードに後述するPublicKey、PrivateKeyを設定することで試すことが出来ます。

## PublicKey、PrivateKeyの作成

web-pushコマンドがインストールされていない場合はPowerShellなどでインストールします
```
npm install web-push -g
```

PublicKey、PrivateKeyを生成
```
web-push generate-vapid-keys
```

生成されたPublicKey(PublicKey),PrivateKey(PrivateKey)を大切に保管してください(GithubなどにもPushしないでください)
```
=======================================

Public Key:
BCrj9mDVHUW7Lw5e1v4OnusRY6A4Z02pwl5nfTex0YjH--o9pUsYHqlAewyWzSysFkq0-EIXDZLs42diK10sKOM

Private Key:
JMHs03rsWmF-adpMCz1ocGOQ5YanmdsiyekkQFI9aAc

=======================================
```

## 組み込み

## Server

WebAPIを作成しWebPush.Serverを参照設定
Program.csに
```
builder.Services.AddWebPush(config =>
{
    config.PublicKey = "{{PublicKey}}";
    config.PrivateKey = "{{PrivateKey}}";
});
```
本来は環境変数から取得してください

新たにControllerを追加(WebPushController.cs)

``` csharp
using BlazorWebPush.Shared;
using Microsoft.AspNetCore.Mvc;
using PushService.Server;

namespace BlazorTest.Server.Controllers;

[Route("api/[controller]")]
[ApiController]
public class WebPushController : ControllerBase
{
    private ILogger<WebPushController> _logger { get; init; }
    private static List<Subscription> _subscriptions { get; } = new();
    private IPushNotification _pushNotification { get; init; }

    public WebPushController(ILogger<WebPushController> logger, IPushNotification pushNotification)
    {
        _logger = logger;
        _pushNotification = pushNotification;

    }

    /// <summary>
    /// 購読追加
    /// </summary>
    /// <param name="subscription"></param>
    /// <returns></returns>
    [HttpPost("Subscribe")]
    public IActionResult Subscribe([FromBody] Subscription subscription)
    {
        try
        {
            _logger.LogInformation("Subscribe: {0}", subscription);
            _subscriptions.Add(subscription);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Subscribe: {0}", subscription);
            return BadRequest();
        }

        return Ok();
    }

    /// <summary>
    /// WebPush通知送信
    /// </summary>
    /// <returns></returns>
    [HttpPost("Send")]
    public async Task<IActionResult> SendAsync()
    {
        try
        {
            _logger.LogInformation("SendAsync");
            foreach (var token in _subscriptions)
            {
                _logger.LogInformation("SendAsync: {0}", token);
                await _pushNotification.PushAsync(token.EndPoint, token.P256dh, token.Auth, "Hello, World!!!!");
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "SendAsync");
            return BadRequest();
        }
        return Ok();
    }
}

```
* SubscribeはClientで購読した情報を登録
* Sendは購読一覧にメッセージを送信


## Client
WebPush.Clientを参照設定します。

購読管理を行うページに

``` html
<WebPushControl @ref="webPushControl" WebPushPublicKey="@webPushPublicKey"></WebPushControl>
```

{{PublicKey}}は置き換えてください
``` csharp
@code{
    private string webPushPublicKey = "{{PublicKey}}";
}
```

購読処理（サンプルではhome.razorにInitボタンで行っています)

``` html
<button @onclick="RequestNotificationSubscriptionAsync">Init</button>
```

```csharp
    public required WebPushControl webPushControl;
    async Task RequestNotificationSubscriptionAsync()
    {
        var subscription = await webPushControl.RequestNotificationSubscriptionAsync();

        // subscriptionがnullじゃない場合Consoleに出力
        if (subscription != null)
        {
            Console.WriteLine(subscription.Url);
            Console.WriteLine(subscription.P256dh);
            Console.WriteLine(subscription.Auth);

            await _webPushUsecase.SubscribeAsync(subscription.Url!, subscription.P256dh!, subscription.Auth!);
        }
    }
```

WebPushControlは機能的にはほぼ何もしておらず、WebPushJsInteropを呼んでいます。
WebPushJsInteropはJavascriptのロードやServiceWorker登録を行っています。

```csharp
    public class WebPushJsInterop : IAsyncDisposable
    {
        private readonly Lazy<Task<IJSObjectReference>> moduleTask;

        public WebPushJsInterop(IJSRuntime jsRuntime)
        {
            moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
                "import", "./_content/WebPush.Client/web-push.js").AsTask());
        }

        public async ValueTask ServiceWorkRegister()
        {
            var module = await moduleTask.Value;
            await module.InvokeVoidAsync("serviceWorkRegister", null);
        }

        public async ValueTask<NotificationSubscription> RequestSubscription(string publicKey)
        {
            var module = await moduleTask.Value;
            return await module.InvokeAsync< NotificationSubscription>("requestSubscription", publicKey);
        }
        public async ValueTask DisposeAsync()
        {
            if (moduleTask.IsValueCreated)
            {
                var module = await moduleTask.Value;
                await module.DisposeAsync();
            }
        }
    }
```


## 動作確認
上記を組み込み、ClientのInitボタンを押すと

許可を求めるダイアログが表示され、許可を押下します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136187/ece00da5-7413-c422-dd80-68a132ff8a4a.png)

[swagger](https://localhost:7127/swagger)を開きSendを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136187/1c9ea8a2-b3fa-0efc-59ed-272a307a2689.png)

画面右下に通知が出ることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136187/3433cf5b-cd03-4a7b-7646-40faaeda822e.png)


### 購読を解除する場合は

:::note info
Edge:設定→Cookieとサイトのアクセス許可→通知を選択し削除してください。
:::

:::note info
Chrome:設定→プライバシーとセキュリティ→サイトの設定→通知
:::

Edgeの場合ダイアログではなく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136187/e1f5bdee-c816-2b48-97a7-d0b0cd85bda8.png)
アドレスバーにベルマークが出る場合があります。
その場合、ベルマークを押すと許可することが出来ます。

## WebPushの内容
WebPushはWebPush.Clientプロジェクトにあるservice-worker.js

```javascript
self.addEventListener('push', event => {
    const payload = event.data.json();
    event.waitUntil(
        self.registration.showNotification('BLAZOR', {
            body: payload.message,
            icon: 'icon-192.png',
            vibrate: [100, 50, 100],
            data: { url: payload.url }
        })
    );
});
```
でおこなっていますので、ここをカスタマイズしてください。



## 参考ページ

https://zenn.dev/akinoringo/articles/1dedede4f1300b

https://github.com/dotnet-presentations/blazor-workshop/blob/main/docs/09-progressive-web-app.md#displaying-notifications

https://github.com/web-push-libs/web-push