Notificaciones push del navegador en Javascript y PHP

Prefacio

En un intento por encontrar un buen artículo sobre la configuración de notificaciones en el navegador, solo recibí artículos que describían principalmente el uso junto con Firebase, pero esta opción no era particularmente adecuada para mí.





En este artículo, los principios de funcionamiento y las sutilezas de las notificaciones push no se "enturbiarán", solo el código, solo el hardcore.





Notas importantes





Las notificaciones push solo funcionan con HTTPS .

Por cierto, además de HTTPS, debe estar presente un certificado SSL válido, Let's Encrypt servirá.





Localhost está bien para el desarrollo. No debería haber ningún problema, pero si surge, este artículo lo ayudará a resolverlo.





Que haya código

Autorización (VAPID)

Primero, vale la pena instalar la biblioteca WebPush en su proyecto php:





$ composer require minishlink/web-push
      
      



A continuación, para autorizar su servidor con un navegador (VAPID), debe generar claves ssh públicas y privadas. Estas claves serán necesarias tanto en el servidor como en el cliente (excepto que solo se necesita la pública en el cliente) .





Para generar una clave pública y privada codificada en Base64 sin comprimir, ingrese lo siguiente en su bash de Linux:





$ openssl ecparam -genkey -name prime256v1 -out private_key.pem
$ openssl ec -in private_key.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt
$ openssl ec -in private_key.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt
      
      



Además, el autor de la biblioteca proporciona la generación de claves vacías utilizando el método incorporado:





$vapidKeysInBase64 = VAPID::createVapidKeys();
      
      



Suscripción

Etapa 1 (JS)

ServiceWorker, PushManager, showNotification :





app.js





function checkNotificationSupported() {
	return new Promise((fulfilled, reject) => {
  	if (!('serviceWorker' in navigator)) {
      reject(new Error('Service workers are not supported by this browser'));
      return;
    }

    if (!('PushManager' in window)) {
      reject(new Error('Push notifications are not supported by this browser'));
      return;
    }

    if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
      reject(new Error('Notifications are not supported by this browser'));
    	return;
    }
    
    fulfilled();
  })
}
      
      



sw.js :





app.js





navigator.serviceWorker.register('sw.js').then(() => {
      console.log('[SW] Service worker has been registered');
    }, e => {
      console.error('[SW] Service worker registration failed', e);
    }
  );
      
      



:





app.js





function checkNotificationPermission() {
    return new Promise((fulfilled, reject) => {
        if (Notification.permission === 'denied') {
            return reject(new Error('Push messages are blocked.'));
        }
        if (Notification.permission === 'granted') {
            return fulfilled();
        }
        if (Notification.permission === 'default') {
            return Notification.requestPermission().then(result => {
                if (result !== 'granted') {
                    reject(new Error('Bad permission result'));
                } else {
                    fulfilled();
                }
            });
        }
        return reject(new Error('Unknown permission'));
    });
}
      
      



ssh :





<script>
	window.applicationServerKey = '<?= $yourPublicKeyFromServer ?>'
</script>
      
      



, . 10 .





app.js





document.addEventListener('DOMContentLoaded', documentLoadHandler);


function documentLoadHandler() {
    checkNotificationSupported()
        .then(() => {
          setTimeout(() => {
            serviceWorkerRegistration.pushManager.subscribe({
                    userVisibleOnly: true,
                    applicationServerKey: urlBase64ToUint8Array(window.applicationServerKey),
                })
                .then(successSubscriptionHandler, errorSubscriptionHandler)
          }, 10000);
         }, 
        	console.error
      	);
}


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

function errorSubscriptionHandler(err) {
    if (Notification.permission === 'denied') {
        console.warn('Notifications are denied by the user.');
    } else {
        console.error('Impossible to subscribe to push notifications', err);
    }
}
      
      



successSubscriptionHandler





.





app.js





function successSubscriptionHandler(subscriptionData) {
    const key = subscription.getKey('p256dh');
    const token = subscription.getKey('auth');
    const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];
    const body = new FormData();

    body.set('endpoint', subscription.endpoint);
    body.set('publicKey', key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null);
    body.set('authToken', token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null);
    body.set('contentEncoding', contentEncoding);

    return fetch('src/push_subscription.php', {
      method,
      body,
    }).then(() => subscription);
  }
      
      







Post Message API





self.addEventListener('push', function (event) {
    if (!(self.Notification && self.Notification.permission === 'granted')) {
        return;
    }

    const sendNotification = body => {
        const title = " ";

        return self.registration.showNotification(title, {
            body,
        });
    };

    if (event.data) {
        const message = event.data.text();
        event.waitUntil(sendNotification(message));
    }
});
      
      



2 (PHP)

php 7+





subscribeUserToPushNotifications ,





subscribeUserToPushNotifications.php





<?php 

$subscription = $_POST;
if (!isset($subscription['endpoint'])) {
    echo 'Error: not a subscription';
    return;
}

// save subscription from => $subscription 
      
      



( ), .









, :





pushNotificationToClient.php





<?php 

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

$subscription = Subscription::create($subscriptionData);
      
      



VAPID :





pushNotificationToClient.php





<?php 

$auth = array(
    'VAPID' => array(
        'subject' => 'https://your-project-domain.com',
        'publicKey' => file_get_contents(__DIR__ . '/your_project/keys/public_key.txt'),
        'privateKey' => file_get_contents(__DIR__ . '/your_project/keys/private_key.txt'), 
    )
);
      
      



, WebPush:





pushNotificationToClient.php





<?php

$webPush = new WebPush($auth);
      
      



! Push





<?php

$report = $webPush->sendOneNotification(
  $subscription,
  "  ,     sw.js"
);
      
      



Nota IMPORTANTE





Para enviar notificaciones en iteración, debe usar una función con los mismos parámetros que en la función anterior:





$webPush->queueNotification







Fuentes útiles

  1. Acerca de la tecnología push





  2. Acerca de WebPush de Khabrovchanin





  3. Biblioteca WebPush





  4. Un ejemplo de uso de un desarrollador de bibliotecas








All Articles