WebEngine 推送通知示例

演示如何订阅和取消订阅推送通知。

在示例中,我们将从网络推送服务向用户发送推送通知。这是一个典型的场景,其中消息从应用服务器(即网站后端)通过第三方推送服务发送,最终以通知的形式到达用户的浏览器。为了演示此流程,我们将实现一个简单的推送服务服务器应用程序,用户可以订阅接收ping消息。

如前所述,此类工作流程中有三方参与

  • 用户的网络浏览器,他们在这里接收推送通知
  • 第三方推送服务,它由订阅端点定义,并是浏览器推送服务实现的一部分
  • 应用服务器,它将存储用户的订阅并使用订阅端点初始化推送消息

用户访问一个网站,其中 JavaScript 网络应用使用 JavaScript Push API 创建推送通知订阅。然后,用户会被要求授权接收和显示推送通知。一旦接受,推送 API 就会与第三方推送服务建立一个推送频道,对于 QtWebEngine 来说,这是 QtWebEngine 中的 Firebase Cloud Messaging (FCM)。创建了一个唯一的推送订阅,其中包含订阅端点 URL。然后,浏览器将订阅消息发送给应用服务器,包括端点设置。应用服务器现在可以使用订阅端点向浏览器发送通知。浏览器推送服务实现会将推送消息投递。但是为了显示,必须注册一个服务工作者。因为服务工作者在后台运行,它允许即使已经安装推送通知的网站不再打开时,也能显示通知。

让我们深入了解实现细节。我们首先用 NodeJS 实现我们的自定义推送服务,使用两个模块

  • web-push - 提供了 web-push 协议实现
  • express - 提供了网络应用程序框架

现在让我们初始化一个新项目,并在此示例的根目录中安装所需的模块

npm init -y
npm install web-push express

这些命令应该创建 package.js,它定义了启动命令

"start": "node server.js"

现在让我们继续在 server.js 中实现推送服务后端。

我们首先引入所需的模块并执行基本 express 框架设置,我们使用它来创建我们的自定义推送服务器。为简单起见,我们一次只处理一个订阅。为此,我们需要创建 VAPID 密钥,我们将使用 web-push 库来生成。公钥将由前端使用并用来验证服务。

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:[email protected]', vapidKeys.publicKey, vapidKeys.privateKey);

注意: 在本例中,我们将不涉及消息加密。

要生成密钥,我们可以使用与 web-push 库一起提供的工具,该工具被安装在示例根目录中,通过 npm 安装。

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

现在我们向推送服务器添加两个 路由。一个用于 订阅,另一个用于 取消订阅,以便我们的前端可以发送 HTTP POST 请求来处理推送订阅。在订阅请求中,我们将在请求体中获得 订阅,同时检索自定义头 ping-time,它定义了 ping 消息应向用户推送的频率。我们保留 订阅 以便以后发送推送通知。作为确认,我们发送 201 状态代码并基于 ping-time 值安排第一次推送通知。简单的 取消订阅 请求只是简单地删除订阅。

// 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 库发送推送消息。我们创建要呈现给用户的消息的有效载荷并安排下一次推送消息。

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 通知消息的频率。我们将有两个按钮:点击 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 来处理订阅。当用户点击 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 添加到 POST 请求的 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() 的样板代码。

作为前端最后的部分,让我们看看服务工作者脚本中的内容,在其中我们简单地注册一个用于 push 事件的监听器。

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

当出现推送事件时,我们简单地使用 Notification JavaScript API 来显示通知。

注意: QtWebEngine 通知示例说明如何提供您自己的处理器并自定义通知消息的外观和感觉。

实现完成之后,我们可以在本地的 5000 端口上启动服务器。为此,我们可以在项目根目录的 console 中简单地输入

npm start

现在我们可以查看基于 WebEngine Notifications Examplepush-notification 浏览器应用程序了。

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::featurePermissionRequested,
                     [&](const QUrl &origin, QWebEnginePage::Feature feature) {
                         if (feature != QWebEnginePage::Notifications)
                             return;

                         view.page()->setFeaturePermission(origin, feature,
                                                           QWebEnginePage::PermissionGrantedByUser);
                     });

    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。我们将不详细介绍如何打开通知,因为已有文档在此。然而,您需要修改应用程序的两种方式。首先,由于推送消息将被禁用,QWebEngineProfile 不能设置成 离线模式。因此,如上所见,QWebEngineProfile 使用名称进行了初始化。其次,您需要通过调用QWebEngineProfile::setPushServiceEnabled 为创建的 profile 启用推送消息。

应用程序运行时显示

在权限授予后,我们可以发送我们的ping请求

我们应该看到即将到来的推送通知

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。本处包含的文档贡献均为各自所有者的版权财产。本提供的文档遵守由自由软件基金会发布的GNU自由文档许可协议版本1.3的条款。Qt及其相关商标是芬兰的Qt公司及其在世界其他国家的商标。所有其他商标均为各自所有者的财产。