shinke1987.net
雑多な備忘録等のはず。
他のカテゴリ・タブ
目次
PR

Laravel11でWebブラウザのプッシュ通知の動作確認

2024-08-16 2024-08-16
カテゴリ: Laravel

概要

Laravel11 と laravel-notification-channels/webpush を利用してWebブラウザのプッシュ通知の動作確認を行う。

参考資料

環境

Laravel Sailを利用した。

MacOS:14.5 Sonoma

PHP:8.3.10

Laravel:11.20.0

動作確認の準備

Laravel Sail の基本セットアップ

下記コマンドを実行する。

// 作業フォルダへ移動。
% cd 作業フォルダ

// Laravelアプリケーションの作成。
% curl -s "https://laravel.build/webpush-test" | bash

// Sailを起動。
% cd webpush-test
% ./vendor/bin/sail up -d

// DBのマイグレーション実行。
% ./vendor/bin/sail artisan migrate

// laravel-notification-channels/webpush をインストール。
% ./vendor/bin/sail composer require laravel-notification-channels/webpush

app/Models/User.php 編集

次に app/Models/User.php を次のように編集する。(2行だけ編集)

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

// ※ HasPushSusbscriptions を追加。
use NotificationChannels\WebPush\HasPushSubscriptions;

class User extends Authenticatable
{
    // ※ HasPushSusbscriptions を追加。
    use HasFactory, Notifiable, HasPushSubscriptions;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

WebPush用マイグレート等実行

次に下記コマンドを実行する。

// マイグレーションファイル生成。
% ./vendor/bin/sail artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider" --tag="migrations"

// マイグレート実行。
% ./vendor/bin/sail artisan migrate

// VAPIDで利用する公開鍵と秘密鍵を生成して .env へ追加。
% ./vendor/bin/sail artisan webpush:vapid

.env 編集

次に .envファイルにて、
MacのSafariでもプッシュ通知を表示させるために、subjectの設定と、
xDebugを動かすための設定を行う。(合計2行追記)

xDebugを利用するにはIDEとphp.iniの編集も必要になる。

php.ini については、Dockerコンテナ内に入り、テキストエディタをインストールし、/etc/php/8.3/cli/conf.d/20-xdebug.ini を編集すれば良い。

IDEはPHPStormやVSCodeで手順が変わるので、適宜調べると良い。

(この動作確認ではPHPStormを利用している)

※ ./vendor/bin/sail up コマンドを利用しないと、xDebugを利用できないので注意。

(Dockerからコンテナ起動すると、xDebug利用できない)

APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:Y0EgytJvxhFQn7RpK0y36Y3K09nYR+LSf//QICzIq0Q=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=sail
DB_PASSWORD=password

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database

CACHE_STORE=database
CACHE_PREFIX=

MEMCACHED_HOST=127.0.0.1

REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://meilisearch:7700

MEILISEARCH_NO_ANALYTICS=false

// ※ 下記の1行を追記。
VAPID_SUBJECT="mailto:メールアドレス"
VAPID_PUBLIC_KEY=BCe3IPJTeTMABsuWjtq3i-uxAgWXtfZ93m_nm3TU7YiIhqmKZMt5hHi5lJD3fdaq5Awif3m9Y-mEdeUvmC1WqhU
VAPID_PRIVATE_KEY=wYKHvNOQ469B75RXHwnXVKvYFA7Dq7Q6B-FIUdDYbkw

// ※ 下記の1行を追記。
SAIL_XDEBUG_MODE=develop,debug

app/Notifications/TestPush.php 作成と編集

次に下記コマンドを実行する。

% ./vendor/bin/sail artisan make:notification TestPush

次に app/Notifications/TestPush.php を次のように編集する。

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Minishlink\WebPush\WebPush;
use NotificationChannels\WebPush\WebPushChannel;
use NotificationChannels\WebPush\WebPushMessage;

class TestPush extends Notification
{
    use Queueable;

    private $title;
    private $body;
    private $url;

    /**
     * Create a new notification instance.
     */
    public function __construct(string $title = "", string $body = "", string $url = "")
    {
        $this->title = $title;
        $this->body = $body;
        $this->url = $url;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @return array<int, string>
     */
    public function via(object $notifiable): array
    {
//        return ['mail'];
        return [WebPushChannel::class];
    }

    public function toWebPush($notifiable, $notification)
    {
        return (new WebPushMessage())
            ->title($this->title)
            ->body($this->body)
            ->data(['url' => $this->url]);
    }

    /**
     * Get the array representation of the notification.
     *
     * @return array<string, mixed>
     */
    public function toArray(object $notifiable): array
    {
        return [
            //
        ];
    }
}

Breeze セットアップ

次のコマンドを実行する。

// Breeze をインストール。
% ./vendor/bin/sail composer require laravel/breeze
% ./vendor/bin/sail artisan breeze:install

// 色々聞かれるが、とりあえず「Blade with Alpine」を選択する。
> Blade with Alpine

// Would you like dark mode support ? と聞かれるが、どちらを選択しても良い。
> No

// Which testing framework do you prefer ? と聞かれるが、どちらを選択しても良い。
> PHPUnit

アカウント作成

次に最初から存在するWelcomeページから、アカウントを1個作成する。

(今回はこのアカウントに紐付けてプッシュ通知を行う)

Webブラウザでクリックと入力してRegister押すだけなので、手順は省略する。

名前やメールアドレスやパスワードを控える必要は無い。

app/Http/Controllers/WebPushController.php 作成と編集

次にコントローラを作成する。

% ./vendor/bin/sail artisan make:controller WebPushController

次に app/Http/Controllers/WebPushController.php を次のように編集する。

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Notifications\TestPush;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class WebPushController extends Controller
{
    public function setSubscription(Request $request): JsonResponse
    {
        $user = User::find(1);
        $tempData = $request->json()->all();
        $user->updatePushSubscription(
            $tempData['endpoint'],
            $tempData['keys']['p256dh'],
            $tempData['keys']['auth'],
            $tempData['contentEncoding']
        );
        return response()->json();
    }

    public function send(Request $request)
    {
        $user = User::find(1);
        $user->notify(new TestPush('タイトル', '内容', 'https://google.co.jp'));
    }
}

routes/web.php 編集

次に routes/web.php を次のように編集する。

<?php

use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\WebPushController;

// ここから
Route::view('/webpush', 'webpush');

Route::post('/set-subscription', [WebPushController::class, 'setSubscription'])
    ->withoutMiddleware(\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class);

Route::get('/send', [WebPushController::class, 'send']);
// ここまで追記。

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});

require __DIR__.'/auth.php';

ビューの編集

次に resources/views/webpush.blade.php を作成し、下記のように編集する。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>webpush.blade.php</title>
    <script src="app.js"></script>
</head>
<body>

webpush.blade.php<br>
<br>
<button
    type="button"
    onclick="getPermission()"
>
    通知の許可を取得
</button>
<br><br>
<button
    type="button"
    onclick="updateSw()"
>
    サービスワーカーを更新
</button>

</body>
</html>

public/app.js 編集

次に public/app.js を作成し、下記のように編集する。

// 公開鍵。
// ※ .envファイルの公開鍵をコピペすれば良い。
const applicationServerKey = 'BCe3IPJTeTMABsuWjtq3i-uxAgWXtfZ93m_nm3TU7YiIhqmKZMt5hHi5lJD3fdaq5Awif3m9Y-mEdeUvmC1WqhU';

// 公開鍵を編集する関数。minishlink/web-pushからコピペした。
function urlBase64ToUint8Array (base64String) {
    const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding).replace(/\-/g, '+').
        replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

async function getPermission() {
    // 通知の許可を取得。
    const result = await Notification.requestPermission();

    if (result !== 'granted') {
        // 許可以外だった場合は何もしない。
        return;
    }

    // サービスワーカーを登録。
    await navigator.serviceWorker.register('webpush_sw.js');

    // アクティブになったサービスワーカーを取得。
    const swr = await navigator.serviceWorker.ready;

    // プッシュサーバへ登録。
    const subscription = await swr.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(applicationServerKey)
    });

    // 暗号化方式を確認。
    let contentEncoding = 'aesgcm';
    if (PushManager.supportedContentEncodings?.includes('aes128gcm')) {
        contentEncoding = 'aes128gcm';
    }

    // アプリケーションサーバへsubscriptionの情報を保存。
    await fetch('set-subscription', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(Object.assign(subscription.toJSON(), {contentEncoding}))
    });

    // 完了した旨表示。
    alert('プッシュサーバの登録まで完了');
}

async function updateSw() {
    const swr = await navigator.serviceWorker.ready;
    swr.update();
}

サービスワーカー(public/webpush_sw.js)の編集

次に public/webpush_sw.js を作成し、下記のように編集する。

// 通知をクリックした時に表示するURL。
let url = null;

self.addEventListener('push', async function (event) {
    const jsonData = event.data.json();

    url = jsonData.data.url;


    event.waitUntil(
        self.registration.showNotification(jsonData.title, {
            body: jsonData.body,
        }),
    );
});

// 通知をクリックされた時のイベント。
self.addEventListener('notificationclick', function (event) {
    event.notification.close();
    clients.openWindow(url);
});

動作確認

  1. ※ xDebugを利用するなら、WebPushControllerのsetSubscriptionメソッドの最初の行にブレイクポイントを設置する。
  2. Webブラウザで http://localhost/webpush へアクセスし、「通知の許可を取得」ボタンを押下する。
  3. Webブラウザで http://localhost/send へアクセスし、通知が表示されることを確認する。
  4. 通知をクリックすると、Googleの検索ページがWebブラウザで表示されることを確認する。

上記以外にも、

といったことを確認すると良い。

同一カテゴリの記事