技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

ServiceWorker について(2)

目標

ServiceWorker でPush通知を受け取る。
内容的には下記の記事の続き。

white-azalea.hatenablog.jp

目次

尚、参考はこれ

qiita.com

フレームワークインストール

まずは nodejs でサーバを作る。
npm init とだけ打ち込んで空のパッケージを作成する。

次にフレームワークをインストールしよう

  • npm i compression
  • npm i express
  • npm i web-push

コレだけですし、一瞬ですかね?

実装

こんな感じの配置でファイルを作る

  • root
    • public
      • js
        • vapid_demo.js : 初期化等諸々
      • index.html : 画面
      • sw.js : サービスワーカー
    • index.js : エントリポイント
    • package.json : nodejs の管理ファイル

各ファイルの実装

index.js

シンプルに実装

const compression = require('compression');
const express = require('express');
const webPush = require('web-push');

const app = express();
const port = 3000;

// サーバ起動時に、秘密鍵と公開鍵を作成
const vapidKeys = webPush.generateVAPIDKeys();

// Push 通知機能を初期化
webPush.setVapidDetails(
    'mailto:xxxxxxx@xxxxx.example',  // メール形式でどうぞ
    vapidKeys.publicKey,
    vapidKeys.privateKey
);

app.use(compression());
app.use(express.json());

// 公開鍵を応答するアクション
app.get('/key', (req, res) => {
  res.status(200).send(vapidKeys.publicKey);
})

// POST を受けたら、5秒後に Push 通知を送信するアクション
app.post('/webpushtest', (req, res) => {
    console.log(req.body);
    try {
        setTimeout(async _ => { // ちょっと遅延させて通知
            await webPush.sendNotification(req.body, JSON.stringify({
                title: 'Web Push通知テスト',
            }));
        }, 5000);
        res.json({ success: true });
    } catch (err) {
        console.log(err);
    }
})

// 上記URLでヒットしないなら、public の中を表示する
app.use('/', express.static('public'));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

index.html

こっちも死ぬほどシンプル

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>VAPID WebPush Demo</title>
</head>
<body>
    <button id="btnWebPushTest">プッシュ通知テスト</button>
    <script src="js/vapid_demo.js"></script>
</body>
</html>

vapid_demo.js

Push 通知を受け取るための初期化を行ってるコード。
因みに、ここの subscription をサーバ側に保存することで、個別に Push 通知出来るっぽい。

let convertedVapidKey, subscription;

(async _ => {
    try {
        const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' });

        // サーバー側で生成したパブリックキーを取得し、urlBase64ToUint8Array()を使ってUit8Arrayに変換
        const res = await fetch('/key');
        const vapidPublicKey = await res.text();
        convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
        console.log(`vapidPublicKey: ${vapidPublicKey}`);

        // (変換した)パブリックキーをapplicationServerKeyに設定してsubscribe
        subscription = await registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: convertedVapidKey
        });
        console.log(`subscription : ${JSON.stringify(subscription)}`);

        // 通知の許可を求める
        Notification.requestPermission(permission => {
            console.log(permission); // 'default', 'granted', 'denied'
        });
    } catch (err) {
        console.log(err);
    }
})();

btnWebPushTest.onclick = async evt => {
    if (!subscription) return console.log('sbuscription is null');
    await fetch('/webpushtest', {
        method: 'POST',
        body: JSON.stringify(subscription),
        headers: {
            'Content-Type': 'application/json',
        },
    });
};

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;
}

最後に、Push 通知を受け取る側

sw.js

self.addEventListener('push', evt => {
    const data = evt.data.json();
    console.log(data);
    const title = data.title;
    const options = {
        body: data.body,
        icon: 'test.jpg'
    }
    evt.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', evt => {
    evt.notification.close();
});

起動してみる

node index.js と叩くとサーバが起動。

http://loadlhost:3000

にアクセスすると

因みに Chrome の開発者ツールを開いたときにこんなエラーが出ている場合、以前の Push 通知の受信設定が生きてるか何かしてると思われる。

その場合は、開発者ツールの「アプリケーション」タブから削除して画面を開き直そう。

成功するとこんなログが吐き出される。

サーバから「vapid」(中身は公開鍵)を受け取って、自己の識別情報、通知先のデータを生成しているのが解る。

Push 通知を送ってみる。

コレをサーバに送信すると、NodeJS 側でコレが5秒後に応答を返す。

app.post('/webpushtest', (req, res) => {
    console.log(req.body);
    try {
        setTimeout(async _ => { // ちょっと遅延させて通知
            await webPush.sendNotification(req.body, JSON.stringify({
                title: 'Web Push通知テスト',
            }));
        }, 5000);
        res.json({ success: true });
    } catch (err) {
        console.log(err);
    }
})

サーバ側で受け取った JSON はコレ

{
  endpoint: 'https://fcm.googleapis.com/fcm/send/f7iyB(中略)',
  expirationTime: null,
  keys: {
    p256dh: 'BN(中略)',
    auth: 'hB2f(中略)'
  }
}

fcm.googleapis.com の FCM は Firebase Clowd Messaging の略らしい。
FCM の仕様はこちらを参照