Web Push y Vue.js, una vez más sobre cómo trabajar con mensajes web push en la interfaz

Después de haber trabajado en la funcionalidad con mensajes web push en el siguiente proyecto, descubrí que todavía no había suficiente información para hacerlo rápidamente y sin preguntas. Por eso, si bien no todo ha desaparecido de mi memoria, me apresuro a formalizar esta experiencia en forma de artículo.



Puede encontrar artículos de 2017 ... 2018 que se centran en el uso de medios de nivel relativamente bajo para enviar y recibir mensajes web push, por ejemplo, utilizando la biblioteca web-push-libs / web-push . Esta biblioteca aún está evolucionando, sin embargo, ahora es mucho más fácil trabajar con bibliotecas de firebase.



Configurar un proyecto de base de fuego



Así que comencemos creando un proyecto en firebase. Con firebase console abierta , es necesario crear un nuevo proyecto. En Información general-> Configuración-> Configuración general-> Sus aplicaciones, debe crear una nueva aplicación web. Esto generará un código de inicialización de la aplicación web en el lado de la interfaz:



<!-- 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>


En la misma pestaña de Firebase console Información general-> Configuración-> Cloud Messaging-> Credenciales para el proyecto -> Clave del servidor , encontramos la clave privada, con la que puedes enviar notificaciones push a través del servidor de firebase.



Envío de un mensaje web push



Los desarrolladores de front-end pueden enviar mensajes web push por su cuenta usando el comando curl:



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


La obtención de una clave de servidor se describe en la sección Cómo configurar un proyecto de base de fuego , y la obtención de un token de registro se describe en la sección Cómo obtener un token de registro .



carga útil de datos vs notificación



La carga útil se puede enviar en el campo de datos o notificación de un mensaje web push. Para la carga útil de notificación, la solicitud se verá así (para la carga útil de datos, consulte la solicitud en la sección Envío de un mensaje push ):



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


La carga útil de datos y notificaciones tiene dos diferencias fundamentales:



  1. La carga útil de notificación tiene un conjunto de campos estrictamente definido, los campos adicionales se ignorarán, mientras que la carga útil de datos envía todos los campos a la interfaz sin limitación.
  2. Si el navegador web está en segundo plano o el enlace activo contiene un sitio de terceros, el envío web de carga útil de notificación muestra un mensaje sin transferir el control a los controladores de eventos onMessage, mientras que el envío web de carga útil de datos siempre transfiere el control a los controladores de eventos onMessage, pero para para mostrar un mensaje, debe crear explícitamente un objeto de notificación. Si el navegador web está en un estado activo y nuestro sitio está abierto en la pestaña activa, entonces el trabajo con los datos y la carga útil de notificación no es diferente.


Creando un objeto de mensajería



Para trabajar en la interfaz con mensajes web push, debe crear un objeto de mensajería:



const messaging = window.firebase.messaging();


En este código, firebaseeste es un objeto global que se crea durante la carga de las bibliotecas de base de fuego y se inicializa en el lado de la interfaz como se describe en Configurar un proyecto de base de fuego . El proyecto se desarrolló en Vue.js. Por lo tanto, conectar scripts a través de un elemento de script html no parecía prometedor. Para conectar estos scripts, usé la biblioteca 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
  });


Obtener un token de registro



Un token de registro es un identificador que identifica de forma única un dispositivo y un navegador web, lo que permite que un mensaje web push se envíe a un dispositivo específico y lo procese un navegador web específico:



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


En algunas circunstancias, el token se puede actualizar. Y necesita manejar el evento de actualización del token:



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


Con respecto a este evento, tengo una pregunta: ¿es relevante? El hecho es que incluso antes de pasar a FCM, el procedimiento de rotación de tokens funcionaba con GCM. Esto se describió en la biblioteca para Android e indirectamente en la descripción de la operación del servidor, donde cada respuesta del servidor contenía tokens canónicos y tenían que ser revisados ​​y cambiados constantemente (sin embargo, resultó que, aparte de mí, rara vez alguien lo seguía). Después de pasar a FCM, un concepto como los tokens canónicos dejó de usarse (probablemente porque en la práctica rara vez se rastreó). En este sentido, los casos en los que puede ocurrir un evento no están del todo claros onTokenRefresh().



Evento OnMessage - versión simplificada



Inmediatamente responderé por qué está simplificado. Haremos al menos dos simplificaciones. 1) Usaremos la carga útil de notificación para recibir y mostrar mensajes si la aplicación está en segundo plano sin trabajo adicional. 2) Olvidar que en dispositivos móviles el sistema de seguridad no permite ejecutar el nuevo operador Notification ().



Entonces, como ya dijimos, para la carga útil de notificación, viene un mensaje web push y se muestra sin la más mínima participación del desarrollador de front-end (por supuesto, después de enviar el token de registro al servidor). Queda por resolver el caso de que el navegador web esté en un estado activo y el sitio esté abierto en una pestaña activa:



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


Manejo del evento de recibir un mensaje web push en segundo plano



En esta sección, comenzaremos a trabajar con un trabajador del servicio. Y esto, entre otras cosas, significa que debe configurar el sitio para que funcione utilizando el protocolo https seguro. Y esto complica de inmediato un mayor desarrollo. Por tanto, para casos sencillos basta con lo ya descrito anteriormente.



Para trabajar con la biblioteca de base de fuego, un archivo llamado firebase-messaging-sw.js. El nombre del archivo puede ser diferente, pero en cualquier caso debe estar ubicado en el directorio raíz debido a las peculiaridades de la protección del navegador web (de lo contrario, este service worker no funcionará para todo el sitio).



Como regla general, también se coloca un controlador de eventos en este archivo notificationclick. Difícilmente puede encontrar algo diferente a este código:



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


Opción para manejar el evento onMessage con service worker



Permítame recordarle que en la sección sobre Evento de mensaje, una versión simplificada, ya hemos descrito cómo manejar los mensajes web push. Pero este método tenía un inconveniente importante: no funcionaba en dispositivos móviles debido a las peculiaridades del sistema de protección del navegador web. Para superar este inconveniente, se inventó una opción de trabajador de servicio, en la que el objeto de notificación ya está incrustado y no es necesario crearlo con el nuevo operador:



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


Bip al recibir un mensaje web push



Debo decir que prácticamente no tenemos control sobre cómo se mostrarán las notificaciones push en varios dispositivos. En algunos casos, este será un mensaje emergente, en otros, el empuje irá inmediatamente a la "placa" del sistema, y ​​si todavía no hay voz, el cliente simplemente lo perderá. Con un svuk, todo es muy difícil. Las especificaciones anteriores incluían un campo de sonido, que anteriormente era responsable del sonido al recibir un mensaje web push, pero actualmente no existe tal propiedad. En este sentido, me propuse el objetivo de realizar una grabación de audio del empuje.



La descripción que se encuentra a veces con la creación de un elemento de audio html y la llamada a su método play () en realidad no funciona debido a las características de seguridad del navegador web (solo se puede llamar con un clic de un usuario real). Pero también existe AudioContext (); trabajaremos con él:



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


Todo está bien, pero todavía tenemos un trabajador de servicio que no tiene un objeto AudioContext (). Recordemos que todos los trabajadores se comunican a través de mensajes. Y luego, recibir eventos del trabajador del servicio se verá así:



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


Por supuesto, para que este código funcione, necesita 1) El navegador está abierto 2) El sitio está abierto (aunque no necesariamente en la pestaña activa). Pero no hay otra forma.



En lugar de un epílogo



Ahora puedes exhalar y decir, eso es todo. Pero ... Todo esto no funciona en safari, y este es otro tema separado y mal documentado, aunque se pueden encontrar varios artículos.



Enlaces útiles



1) habr.com/ru/post/321924



apapacy@gmail.com

24 de agosto de 2020



All Articles