WebEngine プッシュ通知の例

プッシュ通知の購読と購読解除の方法を説明します。

この例では、Web プッシュサービスからユーザーにプッシュ通知を送信します。これは典型的なシナリオで、アプリケーションサーバー、つまりウェブサイトのバックエンドからサードパーティのプッシュサービスを通じてメッセージが送信され、最終的に通知という形でユーザーのブラウザに届きます。このフローを実証するために、簡単なプッシュ・サービス・サーバー・アプリケーションを実装します。

すでに述べたように、このようなワークフローには3つの異なる関係者が存在します:

  • プッシュ通知を受け取るユーザーのウェブブラウザ
  • ブラウザのプッシュサービス実装の一部であり、購読エンドポイントによって定義されるサードパーティプッシュサービス
  • アプリケーションサーバー。ユーザーのサブスクリプションを保存し、サブスクリプションエンドポイントを使用してプッシュメッセージを開始します。

ユーザーはウェブサイトを訪問し、JavaScriptウェブアプリケーションはJavaScriptプッシュAPIを使用してプッシュ通知サブスクリプションを作成する。その後、ユーザーはプッシュ通知を受信して表示する許可を求められます。許可されると、Push API はサードパーティのプッシュサービス(QtWebEngine の場合はFirebase Cloud Messaging (FCM))とプッシュチャネルを確立します。サブスクリプション・エンドポイントURLを含むユニークなプッシュサブスクリプションが作成されます。ブラウザはエンドポイント設定を転送するサブスクリプションメッセージをアプリケーションサーバーに送信します。アプリケーションサーバーはサブスクリプションエンドポイントを使ってブラウザに通知を送ることができます。ブラウザプッシュサービスの実装はプッシュメッセージを配信します。しかし、それを表示するには、サービスワーカーを登録しなければなりません。サービスワーカーはバックグラウンドで実行されるので、サービスワーカーをインストールしたウェブサイトが開かれなくなっても、通知を表示することができます。

実装の詳細を見ていこう。まず、NodeJSを使ってカスタムプッシュサービスを2つのモジュールで実装します:

  • web-push- web-pushプロトコルの実装を提供します。
  • express- ウェブ・アプリケーション・フレームワーク

新しいプロジェクトを初期化し、必要なモジュールをこのサンプルのルート・ディレクトリにインストールしよう:

npm init -y
npm install web-push express

これらのコマンドによって、startコマンドを定義するpackage.jsが作成されるはずです:

"start": "node server.js"

それでは、server.jsでプッシュ・サービスのバックエンドの実装に移りましょう。

まず、必要なモジュールを組み込み、expressフレームワークの基本的なセットアップを行います。簡単にするために、一度に1つのサブスクリプションだけを扱うことにする。そのためには、web-pushlibsで生成するVAPIDキーを作成する必要があります。公開鍵はフロントエンドで使用され、サービスを認証します。

const express = require('express');
const webpush = require('web-push');

// setup server
const port = 5000;
const server = express();
server.use(express.json());
server.use(express.static('content'));

// we support only one subscription at the time
var subscription = null;

// setup vapid keys
const vapidKeys = {
    publicKey :
            "BNO4fIv439RpvbReeABNlDNiiBD2Maykn7EVnwsPseH7-P5hjnzZLEfnejXVP7Zt6MFoKqKeHm4nV9BHvbgoRPg",
    privateKey : "HqhrzsRfG5-SB3j45lyUmV7cYZuy-71r2Bb0tgaOefk"
};

// set vapid keys for webpush libs
webpush.setVapidDetails('mailto:push@qt.io', vapidKeys.publicKey, vapidKeys.privateKey);

注: この例ではメッセージの暗号化については触れません。

鍵を生成するには、web-pushlibに同梱されているツールを使います。このツールはnpm 、このサンプルのルートディレクトリにインストールされています。

./node_modules/.bin/web-push generate-vapid-keys

ここで、プッシュ・サーバーに2つのroutessubscribe unsubscribeフロントエンドがプッシュ購読を処理するためにHTTP POSTリクエストを送信できるようにするためです。subscribeリクエストでは、リクエストボディにsubscription 。また、ユーザーにpingメッセージをプッシュする頻度を定義するカスタムヘッダping-time 。後でプッシュ通知を送信できるように、subscription 。確認として、201ステータスコードを送信し、ping-time の値に基づいて最初のプッシュ通知をスケジュールします。unsubscribe リクエストは単にサブスクリプションを削除します。

// add subscribe route
server.post('/subscribe', (req, res) => {

    // subscription request
    subscription = req.body;
    const delay = req.headers['ping-time'];
    console.log('Got subscription with endpoint: ' + subscription.endpoint);
    console.log('Ping delay is at: ' + delay);

    // confirm resource created
    res.status(201).json({});

    // schedule notification
    setTimeout(() => { sendNotification(delay) }, delay * 1000);
});

// add unsubscribe route
server.post('/unsubscribe', (req, res) => {
    console.log('Got unsubscribe with endpoint: ' + req.body.endpoint);
    subscription = null;
    res.status(201).json({});
});

sendNotication() 関数は web-push lib を使用してプッシュメッセージを送信します。ユーザーに提示したいメッセージでペイロードを作成し、次のプッシュ・メッセージをスケジュールします。

function sendNotification(delay)
{
    if (!subscription)
        return;

    // create payload text
    const payload = JSON.stringify({ title : 'Ping !', text : 'Visit qt.io', url : 'www.qt.io' });

    // send notification
    console.log('Sending notification !');
    webpush.sendNotification(subscription, payload).catch(err => console.error(err));

    // schedule next notification
    setTimeout(() => { sendNotification(delay) }, delay * 1000);
}

最後に、指定されたポートでリッスンするサーバーを起動します。

server.listen(port, () => console.log(`Push server started at port ${port}`));

フロントエンドに移りましょう。シンプルなページindex.htmlを作成し、ユーザーがping通知メッセージを受け取る頻度を入力できるようにします。2つのボタンがあります:プッシュ通知を購読するためのPing Meと購読を解除するためのClearです。最後にping.jsをロードします。

<body>
  <h1>Push Notification Using NodeJS and QtWebEngine</h1>
  <div id="app">
    <div id="ping-setup">
      <form>
        <div>
          Ping Me Every [sec]:
        </div>
        <div class="ping-input">
          <input type="number" name="seconds" min="0" max="3600" required="">
        </div><button>Ping Me</button>
      </form>
    </div>
    <div id="ping-clear">
      <div id="ping-text"></div><button id="ping-clear-button">Clear</button>
    </div>
  </div>
  <script src="ping.js"></script>
</body>

最後の部分は、フロントエンド内でプッシュ購読のロジックを作成することです。ここでは、setupclear という2つの関数を用意し、購読を処理します。ユーザーがPing Meボタンをクリックすると、setup 。通知を受け取るためには、サービスワーカーが必要です。こうすることで、ユーザーはウェブサイトを離れても、サービスワーカーがバックグラウンドで動作し、着信メッセージを処理するため、通知を受け取ることができます。そのためには、まずサービスワーカーを登録する必要があります:

    const register = await navigator.serviceWorker.register('/worker.js', { scope : '/' });

cpushManager.subscribe() への呼び出しは、ユーザーに表示される許可プロンプトをトリガーします。権限が付与されると、プッシュサブスクリプションが返されます。これには、ブラウザに通知を送信できるURLエンドポイントが含まれ、登録されたサービスワーカーはプッシュメッセージを待ちます。

    var subscription = await register.pushManager.subscribe(
            { userVisibleOnly : true, applicationServerKey : publicVapidKey });

前述のように、サブスクリプションはFCM用に作成され、HTTP POSTリクエストでカスタムサーバーに送信されます。さらに、ユーザーがウェブサイトで入力したping-time を HTTP ヘッダーに追加します。

    await fetch('/subscribe', {
        method : 'POST',
        body : JSON.stringify(subscription),
        headers : { 'content-type' : 'application/json', 'ping-time' : delay }
    });

関数clear の呼び出しは、まずHTTP POSTリクエストを送信してプッシュ・サーバーから購読を解除し、その後でサードパーティのプッシュ・サービス(FCM)から購読を解除します。

    const register = await navigator.serviceWorker.getRegistration();
    var subscription = await register.pushManager.getSubscription();
    ...
    await fetch('/unsubscribe', {
        method : 'POST',
        body : JSON.stringify(subscription),
        headers : { 'content-type' : 'application/json' }
    });
    ...
    await subscription.unsubscribe();

ping.jsの残りのコードは、ユーザーが入力した値を読み取り、setup() またはclear() を呼び出すための定型的なコードです。

フロントエンドの最後の部分として、サービス・ワーカー・スクリプトの内部を見てみましょう。ここでは、プッシュ・イベントのイベント・リスナーを登録するだけです。

self.addEventListener('push', event => {
    const data = event.data.json();
    self.registration.showNotification(data.title, { body : data.text });
});

プッシュイベントが来たら、Notification JavaScript API を使って通知を表示するだけです。

注: QtWebEngine Notification Example は、独自のハンドラを提供し、通知メッセージのルックアンドフィールをカスタマイズする方法を示しています。

実装ができたので、localhostのポート5000でサーバーを起動することができる。そのためには、プロジェクトのルート・ディレクトリにあるコンソールに入るだけです:

npm start

これで、WebEngine Notifications Example をベースにしたプッシュ通知ブラウザアプリケーションを見ることができます:

int main(int argc, char *argv[])
{
    QCoreApplication::setOrganizationName("QtExamples");
    QApplication app(argc, argv);

    const QString name =
            QString::fromLatin1("push-notifications.%1").arg(qWebEngineChromiumVersion());

    QScopedPointer<QWebEngineProfile> profile(new QWebEngineProfile(name));
    QWebEngineView view(profile.data());
    auto popup = new NotificationPopup(&view);

    QObject::connect(view.page(), &QWebEnginePage::permissionRequested,
                     [&](QWebEnginePermission permission) {
                         if (permission.permissionType() != QWebEnginePermission::PermissionType::Notifications)
                             return;

                         permission.grant();
                     });

    profile->setPushServiceEnabled(true);
    profile->setNotificationPresenter([&](std::unique_ptr<QWebEngineNotification> notification) {
        popup->present(notification);
    });

    view.resize(640, 480);
    view.setUrl(QUrl("http://localhost:5000"));
    view.show();
    return app.exec();
}

このアプリケーションはhttp:\\localhost:5000 のページを開くだけです。通知を開く方法については、ここに文書化されているので詳しく説明しません。ただし、2 つの方法でアプリケーションを変更する必要があります。第一に、QWebEngineProfile 、プッシュ・メッセージングが無効になるため、off-the-recordに設定することはできません。そのため、上にあるように、QWebEngineProfile は名前で初期化されます。第二に、作成されたprofile に対して、QWebEngineProfile::setPushServiceEnabled というコールでプッシュ・メッ セージを有効にする必要がある。

アプリケーションを実行すると、このように表示される:

許可を与えた後、pingリクエストを送信することができます:

と表示されます:

プロジェクト例 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.