Web Push和Vue.js,再次在前端使用Web Push消息

在下一个项目中通过Web推送消息使用该功能后,我发现仍然没有足够的信息来快速且毫无疑问地进行操作。因此,虽然一切都没有从我的记忆中消失,但我还是以文章的形式将这种经历形式化。



您可以找到日期为2017年... 2018年的文章,这些文章专注于使用相对较低级别的手段来发送和接收Web推送消息,例如,使用web-push-libs / web-push该库仍在开发中,但是如今使用Firebase中的库要容易得多。



设置一个firebase项目



因此,让我们从在Firebase上创建一个项目开始。随着火力控制台打开一个新项目需要创建。常规信息->设置->常规设置->您的应用程序中,您需要创建一个新的Web应用程序。这将在前端生成一个Web应用程序初始化代码:



<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.19.0/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.19.0/firebase-analytics.js"></script>

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "...",
    authDomain: "...",
    databaseURL: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "...",
    appId: "...",
    measurementId: "..."
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  firebase.analytics();
</script>


在firebase控制台的“常规信息”->“设置”->“云消息传递”->“项目凭据”->“服务器密钥”的同一选项卡上,我们找到了私钥,您可以使用该私钥通过firebase服务器发送推送通知。



发送网络推送消息



前端开发人员可以使用curl命令自行发送Web推送消息:



curl -X POST -H "Authorization: key=< >" -H "Content-Type: application/json"    -d '{
    "data": {
        "title": "FCM Message",
        "body": "This is an <i>FCM Message</i>",
        "icon": "/static/plus.png",
        "sound": "/static/push.mp3",
        "click_action": "https://google.com",
  },
  "to": "< >"
}' https://fcm.googleapis.com/fcm/send


设置Firebase项目中 描述了获取服务器密钥的过程,获取注册令牌中将描述获取注册令牌的过程



数据与通知有效负载



有效负载可以在Web推送消息的数据或通知字段中发送。对于通知有效负载,请求将如下所示(对于数据有效负载,请参阅“发送推送消息”部分中的请求):



curl -X POST -H "Authorization: key=< >" -H "Content-Type: application/json"    -d '{
    "notification": {
        "title": "FCM Message",
        "body": "This is an <i>FCM Message</i>",
        "icon": "/static/plus.png",
        "click_action": "https://google.com",
  },
  "to": "< >"
}' https://fcm.googleapis.com/fcm/send


数据和通知有效负载有两个基本区别:



  1. 通知有效负载具有一组严格定义的字段,多余的字段将被忽略,而数据有效负载将所有字段无限制地发送到前端。
  2. 如果Web浏览器在后台或活动链接包含第三方站点,则通知有效负载Web推送将显示一条消息,而不将控制权转移到onMessage事件处理程序,而数据有效负载Web推送始终将控制权转移到onMessage事件处理程序,但对于要显示一条消息,必须显式创建一个Notification对象。如果网络浏览器处于活动状态,并且我们的站点在活动选项卡上处于打开状态,则处理数据和通知有效负载的操作不会有所不同。


创建消息传递对象



要在前端处理Web推送消息,您需要创建一个消息传递对象:



const messaging = window.firebase.messaging();


在此代码中,firebase这是一个全局对象该对象是在加载Firebase库的过程中创建的,并在前端侧初始化,如设置firebase项目中所述该项目是在Vue.js中开发的。因此,通过html脚本元素连接脚本看起来没有希望。为了连接这些脚本,我使用了库vue-plugin-load-script



import Vue from "vue";
import LoadScript from "vue-plugin-load-script";

Vue.use(LoadScript);

var firebaseConfig = {
  apiKey: "...",
  authDomain: "...",
  databaseURL: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "...",
  measurementId: "..."
};

Promise.resolve()
  .then(() =>
    Vue.loadScript(
      "https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js"
    )
  )
  .then(() =>
    Vue.loadScript(
      "https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js"
    )
  )
  .then(() =>
    Vue.loadScript(
      "https://www.gstatic.com/firebasejs/7.14.0/firebase-analytics.js"
    )
  )
  .then(() => {
    window.firebase.initializeApp(firebaseConfig);
    const messaging = window.firebase.messaging();
    ... //    messaging
  });


获取注册令牌



注册令牌是唯一标识设备和Web浏览器的标识符,因此允许将Web推送消息发送到特定设备并由特定Web浏览器进行处理:



  Notification.requestPermission()
    .then(permission => {
      if (permission === "granted") {
        messaging
          .getToken()
          .then(token => {
            ... //    
          });
      } else {
        console.log("Unable to get permission to notify.");
      }
    });


在某些情况下,可以更新令牌。并且您需要处理令牌更新事件:



  messaging.onTokenRefresh(function() {
    messaging
      .getToken()
      .then(function(refreshedToken) {
         ... //     
      });
  });


关于此事件,我有一个问题-是否相关。事实是,即使在移至FCM之前,令牌轮换程序仍可与GCM一起使用。这在Android库中进行了描述,并在服务器操作的描述中间接进行了描述,其中每个服务器响应都包含规范标记,并且必须不断对其进行检查和更改(但是事实证明,除了我以外,很少有人跟随它)。转移到FCM之后,规范令牌这样的概念就不再使用了(很可能是因为在实践中很少跟踪它们)。就这一点而言,可能发生事件的情况尚不完全清楚onTokenRefresh()



OnMessage事件-简化版



我将立即回答为什么将其简化。我们将至少进行两个简化。1)如果应用程序在后台运行,我们将使用通知有效负载来接收和显示消息,而无需进行其他工作。2)忘记了,在移动设备上,安全系统不允许执行新的Notification()运算符。



因此,正如我们已经说过的,对于通知有效负载,Web推送消息出现并在没有前端开发人员丝毫参与的情况下显示(当然,在将注册令牌发送到服务器之后)。在Web浏览器处于活动状态并且站点在活动选项卡上处于打开状态的情况下,仍有待解决:



  messaging.onMessage(function(payload) {
      const data = { ...payload.notification, ...payload.data };
      const notificationTitle = data.title;
      const notificationOptions = {
          body: data.body,
          icon: data.icon,
          image: data.image,
          click_action: data.click_action,
          requireInteraction: true,
          data
      };
      new Notification(payload.notification.title, payload.notification);
  });


在后台处理接收Web推送消息的事件



在本节中,我们将开始与服务人员一起工作。除此之外,这意味着您需要配置站点以使用安全的https协议进行工作。这立即使进一步的开发复杂化。因此,对于简单的情况,前面已经描述的内容就足够了。



要使用firebase库,请使用一个名为的文件firebase-messaging-sw.js该文件的名称可以不同,但​​是由于Web浏览器保护的工作方式,无论如何它都必须位于根目录中(否则,此服务工作者将无法在整个站点上工作)。



通常,事件处理程序也放置在此文件中notificationclick您几乎找不到与此代码不同的内容:



var firebaseConfig = {
  apiKey: "...",
  authDomain: "...",
  databaseURL: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "...",
  measurementId: "..."
};

importScripts("https://www.gstatic.com/firebasejs/7.17.2/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/7.17.2/firebase-messaging.js");

firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

messaging.setBackgroundMessageHandler(function(payload) {
  const data = { ...payload.notification, ...payload.data };
  const notificationTitle = data.title;
  const notificationOptions = {
    body: data.body,
    icon: data.icon,
    image: data.image,
    requireInteraction: true,
    click_action: data.click_action,
    data
  };
  self.registration.showNotification(notificationTitle, notificationOptions);
});

self.addEventListener("notificationclick", function(event) {
  const target = event.notification.data.click_action;
  event.notification.close();
  event.waitUntil(
    clients
      .matchAll({
        type: "window",
        includeUncontrolled: true
      })
      .then(function(clientList) {
        for (var i = 0; i < clientList.length; i++) {
          var client = clientList[i];
          console.log(client.url, client.focus);
          if (client.url === target && "focus" in client) {
            return client.focus();
          }
        }
        return clients.openWindow(target);
      })
  );
});


与服务人员一起处理onMessage事件的选项



让我 提醒您,在onMessage Event(简化版本)部分中,我们已经描述了如何处理Web推送消息。但是这种方法有一个重大缺陷-由于Web浏览器保护系统的特殊性,它无法在移动设备上运行。为了克服此缺点,发明了一种服务工作者选项,其中已经嵌入了Notification对象,并且不需要使用新的运算符创建它:



  messaging.onMessage(function(payload) {
    play();
    navigator.serviceWorker.register("/firebase-messaging-sw.js");
    Notification.requestPermission(function(result) {
      if (result === "granted") {
        navigator.serviceWorker.ready
          .then(function(registration) {
            const data = { ...payload.notification, ...payload.data };
            const notificationTitle = data.title;
            const notificationOptions = {
              body: data.body,
              icon: data.icon,
              image: data.image,
              click_action: data.click_action,
              requireInteraction: true,
              data
            };
            return registration.showNotification(
              notificationTitle,
              notificationOptions
            );
          })
          .catch(function(error) {
            console.log("ServiceWorker registration failed", error);
          });
      }
    });
  });


收到网络推送消息时发出蜂鸣声



我必须说,我们几乎无法控制在各种设备上如何显示推送通知。在某些情况下,这将是一条弹出消息,在其他情况下,该推送将立即落入系统的“面板”中,并且如果仍然没有声音在起作用,它将简单地丢失给客户端。使用svuk,一切都很困难。较早的规范包括声场,该场以前负责接收Web推送消息时的声音,但目前没有这种属性。在这方面,我为自己设定了录音的目标。



由于Web浏览器的安全功能,有时在创建html音频元素并调用它的play()方法时有时会遇到描述,但由于Web浏览器的安全功能而无法使用(只能在来自真实用户的点击下才能调用它)。但是还有AudioContext()-我们将使用它:



const play = () => {
  try {
    const context = new AudioContext();
    window
      .fetch(soundUrl)
      .then(response => response.arrayBuffer())
      .then(arrayBuffer => context.decodeAudioData(arrayBuffer))
      .then(audioBuffer => {
        const source = context.createBufferSource();
        source.buffer = audioBuffer;
        source.connect(context.destination);
        source.start();
      });
  } catch (ex) {
    console.log(ex);
  }
};


一切都很好,但是我们仍然有一个没有AudioContext()对象的服务工作者。让我们记住,所有工作人员都是通过消息进行沟通的。然后从服务人员接收事件将如下所示:



try {
  const broadcast = new BroadcastChannel("play");
  broadcast.onmessage = play;
} catch (ex) {
  console.log(ex)  ;
}


当然,要使此代码正常工作,您需要1)打开浏览器2)网站已打开(尽管不一定在活动选项卡上)。但是没有其他办法。



而不是后记



现在您可以呼气说,就是这样。但是...所有这些都不适用于野生动物园-尽管可以找到几篇文章,但这是另一个单独且文献记录很少的话题。



有用的链接



1)habr.com/ru/post/321924



apapacy@gmail.com

2020年8月24日



All Articles