El primer paso es obtener el permiso del usuario para enviarle mensajes push y, luego, podemos obtener un PushSubscription
.
La API de JavaScript para hacerlo es bastante sencilla, así que analicemos el flujo de lógica.
Detección de atributos
Primero, debemos comprobar si el navegador actual es compatible con los mensajes push. Podemos verificar si la función push es compatible con dos verificaciones simples.
- Busca serviceWorker en navigator.
- Busca PushManager en window.
if (!('serviceWorker' in navigator)) {
// Service Worker isn't supported on this browser, disable or hide UI.
return;
}
if (!('PushManager' in window)) {
// Push isn't supported on this browser, disable or hide UI.
return;
}
Si bien la compatibilidad del navegador crece rápidamente para el trabajador de servicio y los mensajes push, siempre es una buena idea detectar funciones para ambos y mejorar de forma progresiva.
Registra un service worker
Con el detector de funciones, sabemos que se admiten los trabajadores del servicio y los envíos. El siguiente paso es “registrar” nuestro service worker.
Cuando registramos un service worker, le indicamos al navegador dónde se encuentra nuestro archivo. El archivo sigue siendo solo JavaScript, pero el navegador le “dará acceso” a las APIs del service worker, incluido el push. Para ser más exactos, el navegador ejecuta el archivo en un entorno de trabajador de servicio.
Para registrar un trabajador de servicio, llama a navigator.serviceWorker.register()
y pasa la ruta de acceso a nuestro archivo. Sería algo como lo siguiente:
function registerServiceWorker() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function (err) {
console.error('Unable to register service worker.', err);
});
}
Esta función le indica al navegador que tenemos un archivo de service worker y dónde se encuentra. En este caso, el archivo del service worker se encuentra en /service-worker.js
. En segundo plano, el navegador realizará los siguientes pasos después de llamar a register()
:
Descarga el archivo del servicio trabajador.
Ejecuta el código JavaScript.
Si todo se ejecuta correctamente y no hay errores, se resolverá la promesa que muestra
register()
. Si hay errores de cualquier tipo, se rechazará la promesa.
Si
register()
lo rechaza, vuelve a verificar si hay errores tipográficos o de sintaxis en tu código JavaScript en Chrome DevTools.
Cuando se resuelve register()
, muestra una ServiceWorkerRegistration
. Usaremos este registro para acceder a la API de PushManager.
Compatibilidad del navegador con la API de PushManager
Cómo solicitar permiso
Registramos nuestro trabajador de servicio y estamos listos para suscribir al usuario. El siguiente paso es obtener su permiso para enviarle mensajes push.
La API para obtener permiso es relativamente simple. El inconveniente es que recientemente cambió de tomar una devolución de llamada a mostrar una promesa. El problema con esto es que no podemos saber qué versión de la API implementa el navegador actual, por lo que debes implementar ambas y controlarlas.
function askPermission() {
return new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
}).then(function (permissionResult) {
if (permissionResult !== 'granted') {
throw new Error("We weren't granted permission.");
}
});
}
En el código anterior, el fragmento de código importante es la llamada a Notification.requestPermission()
. Este método mostrará un mensaje al usuario:
Una vez que el usuario haya interactuado con la solicitud de permiso presionando Permitir, Bloquear o simplemente cerrándola, se nos mostrará el resultado como una cadena: 'granted'
, 'default'
o 'denied'
.
En el código de ejemplo anterior, la promesa que muestra askPermission()
se resuelve si se otorga el permiso. De lo contrario, arrojamos un error que hace que se rechace la promesa.
Un caso límite que debes controlar es si el usuario hace clic en el botón “Bloquear”. Si esto ocurre, tu app web no podrá volver a solicitarle permiso al usuario. Tendrá que “desbloquear” tu app de forma manual cambiando su estado de permiso, que se encuentra oculto en un panel de configuración. Piensa cuidadosamente cómo y cuándo le pedirás permiso al usuario, ya que, si hace clic en bloquear, no es fácil revertir esa decisión.
La buena noticia es que la mayoría de los usuarios están dispuestos a otorgar permisos, siempre que saben por qué se les solicita.
Más adelante, veremos cómo algunos sitios populares solicitan permiso.
Cómo suscribir a un usuario con PushManager
Una vez que tengamos registrado nuestro service worker y tengamos permiso, podremos suscribir a un usuario llamando a registration.pushManager.subscribe()
.
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
Cuando llamamos al método subscribe()
, pasamos un objeto options, que consta de parámetros opcionales y obligatorios.
Veamos todas las opciones que podemos pasar.
Opciones de userVisibleOnly
Cuando se agregó push a los navegadores por primera vez, no había certeza sobre si los desarrolladores debían poder enviar un mensaje push y no mostrar una notificación. Por lo general, se conoce como push silencioso, ya que el usuario no sabe que ocurrió algo en segundo plano.
La preocupación era que los desarrolladores pudieran hacer cosas desagradables, como hacer un seguimiento de la ubicación de un usuario de forma continua sin que este lo sepa.
Para evitar esta situación y darles tiempo a los autores de especificaciones para que consideren la mejor manera de admitir esta función, se agregó la opción userVisibleOnly
y pasar un valor de true
es un acuerdo simbólico con el navegador de que la app web mostrará una notificación cada vez que se reciba un mensaje push (es decir, no hay un mensaje push silencioso).
Por el momento, debes pasar un valor de true
. Si no incluyes la clave userVisibleOnly
o pasas false
, recibirás el siguiente error:
Actualmente, Chrome solo admite la API de Push para suscripciones que generarán mensajes visibles para el usuario. Para indicar esto, puedes llamar a pushManager.subscribe({userVisibleOnly: true})
en su lugar. Consulta https://goo.gl/yqv4Q4 para obtener más información.
Actualmente, parece que los mensajes push silenciosos generales nunca se implementarán en Chrome. En cambio, los autores de especificaciones están explorando la noción de una API de presupuesto que permitirá a las apps web una cierta cantidad de mensajes push silenciosos en función del uso de una app web.
Opción applicationServerKey
Mencionamos brevemente las "claves del servidor de aplicaciones" en la sección anterior. Un servicio push usa las "claves de servidor de la aplicación" para identificar la aplicación que suscribe a un usuario y garantizar que la misma aplicación le envíe mensajes a ese usuario.
Las claves del servidor de aplicaciones son un par de claves públicas y privadas que son únicas para tu aplicación. La clave privada debe mantenerse en secreto para tu aplicación, y la clave pública se puede compartir libremente.
La opción applicationServerKey
que se pasa a la llamada subscribe()
es la clave pública de la aplicación. El navegador pasa esto a un servicio de envío cuando se suscribe al usuario, lo que significa que el servicio de envío puede vincular la clave pública de tu aplicación con el PushSubscription
del usuario.
En el siguiente diagrama, se ilustran estos pasos.
- Tu app web se carga en un navegador y llamas a
subscribe()
, pasando tu clave pública del servidor de aplicaciones. - Luego, el navegador realiza una solicitud de red a un servicio push, el cual generará un extremo, lo asociará con la clave pública de la aplicación y lo enviará al navegador.
- El navegador agregará este extremo a
PushSubscription
, que se muestra a través de la promesasubscribe()
.
Más adelante, cuando quieras enviar un mensaje push, deberás crear un encabezado de Authorization que contenga información firmada con la clave privada del servidor de tu aplicación. Cuando el servicio de notificaciones push recibe una solicitud para enviar un mensaje push, puede validar este encabezado Authorization firmado buscando la clave pública vinculada al extremo que recibe la solicitud. Si la firma es válida, el servicio de push sabe que debe provenir del servidor de aplicaciones con la clave privada coincidente. Básicamente, es una medida de seguridad que evita que cualquier otra persona envíe mensajes a los usuarios de una aplicación.
Técnicamente, applicationServerKey
es opcional. Sin embargo, la implementación más fácil en Chrome la requiere, y es posible que otros navegadores también lo requieran en el futuro. Es opcional en Firefox.
La especificación que define qué debe ser la clave del servidor de aplicaciones es la especificación de VAPID. Cada vez que leas algo que haga referencia a "claves del servidor de aplicaciones" o "claves de VAPID", recuerda que son lo mismo.
Cómo crear claves de servidor de aplicaciones
Para crear un conjunto público y privado de claves del servidor de aplicaciones, visita web-push-codelab.glitch.me o usa la línea de comandos web-push para generar claves. Para ello, haz lo siguiente:
$ npm install -g web-push
$ web-push generate-vapid-keys
Solo debes crear estas claves una vez para tu aplicación. Solo asegúrate de mantener la clave privada en privado. (Sí, lo acabo de decir).
Permisos y subscribe()
Llamar a subscribe()
tiene un efecto secundario. Si tu app web no tiene permisos para mostrar notificaciones cuando llama a subscribe()
, el navegador te solicitará los permisos. Esto es útil si tu IU funciona con este flujo, pero si deseas tener más control (y creo que la mayoría de los desarrolladores lo harán), quédate con la API de Notification.requestPermission()
que usamos antes.
¿Qué es una PushSubscription?
Llamamos a subscribe()
, pasamos algunas opciones y, a cambio, obtenemos una promesa que se resuelve en un PushSubscription
, lo que genera un código como el siguiente:
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
El objeto PushSubscription
contiene toda la información necesaria para enviar un mensaje push a ese usuario. Si imprimes el contenido con JSON.stringify()
, verás lo siguiente:
{
"endpoint": "https://some.pushservice.com/something-unique",
"keys": {
"p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
"auth":"FPssNDTKnInHVndSTdbKFw=="
}
}
endpoint
es la URL de los servicios push. Para activar un mensaje push, realiza una solicitud POST a esta URL.
El objeto keys
contiene los valores que se usan para encriptar los datos de los mensajes enviados con un mensaje push (que analizaremos más adelante en esta sección).
Volver a suscribirse periódicamente para evitar el vencimiento
Cuando te suscribes a las notificaciones push, sueles recibir un PushSubscription.expirationTime
de null
. En teoría, esto significa que la suscripción nunca vence (a diferencia de cuando recibes un DOMHighResTimeStamp
, que te indica el momento exacto en que vence la suscripción). Sin embargo, en la práctica, es común que los navegadores permitan que las suscripciones venzan, por ejemplo, si no se recibieron notificaciones push durante un período prolongado o si el navegador detecta que el usuario no está usando una app que tenga el permiso de notificaciones push. Un patrón para evitar esto es volver a suscribir al usuario cada vez que recibe una notificación, como se muestra en el siguiente fragmento. Esto requiere que envíes notificaciones con la frecuencia suficiente para que el navegador no venza automáticamente la suscripción. Además, debes sopesar con mucho cuidado las ventajas y desventajas de las necesidades legítimas de notificaciones en comparación con el envío de spam al usuario de forma involuntaria solo para que no venza la suscripción. Al final, no deberías intentar luchar contra el navegador para proteger al usuario de las suscripciones a notificaciones olvidadas durante mucho tiempo.
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
Envía una suscripción a tu servidor
Una vez que tengas una suscripción push, deberás enviarla a tu servidor. La manera de hacerlo depende de ti, pero una pequeña sugerencia es usar JSON.stringify()
para obtener todos los datos necesarios del objeto de suscripción. Como alternativa, puedes obtener el mismo resultado de forma manual de la siguiente manera:
const subscriptionObject = {
endpoint: pushSubscription.endpoint,
keys: {
p256dh: pushSubscription.getKeys('p256dh'),
auth: pushSubscription.getKeys('auth'),
},
};
// The above is the same output as:
const subscriptionObjectToo = JSON.stringify(pushSubscription);
El envío de la suscripción se realiza desde la página web de la siguiente manera:
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription),
})
.then(function (response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function (responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
El servidor de nodos recibe esta solicitud y guarda los datos en una base de datos para usarlos más adelante.
app.post('/api/save-subscription/', function (req, res) {
if (!isValidSaveRequest(req, res)) {
return;
}
return saveSubscriptionToDatabase(req.body)
.then(function (subscriptionId) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({data: {success: true}}));
})
.catch(function (err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message:
'The subscription was received but we were unable to save it to our database.',
},
}),
);
});
});
Con los detalles de PushSubscription
en nuestro servidor, podemos enviarle un mensaje al usuario cuando queramos.
Resubscripción periódica para evitar el vencimiento
Cuando te suscribes a las notificaciones push, sueles recibir un PushSubscription.expirationTime
de null
. En teoría, esto significa que la suscripción nunca vence (a diferencia de cuando recibes un DOMHighResTimeStamp
, que te indica el momento exacto en que vence la suscripción). Sin embargo, en la práctica, es común que los navegadores sigan permitiendo que las suscripciones venzan, por ejemplo, si no se recibieron notificaciones push durante mucho tiempo o si el navegador detecta que el usuario no está usando la app que tiene el permiso de notificaciones push. Un patrón para evitar esto es volver a suscribir al usuario cada vez que recibe una notificación, como se muestra en el siguiente fragmento. Para ello, debes enviar notificaciones con la frecuencia necesaria para que el navegador no venza automáticamente la suscripción, y debes sopesar cuidadosamente las ventajas y desventajas de las necesidades legítimas de notificaciones con respecto al envío de spam al usuario para que la suscripción no caduque. Al final, no deberías intentar luchar contra el navegador para proteger al usuario de las suscripciones a notificaciones olvidadas durante mucho tiempo.
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
Preguntas frecuentes
Estas son algunas preguntas frecuentes que las personas hicieron en este punto:
¿Puedo cambiar el servicio push que usa un navegador?
No. El navegador selecciona el servicio push y, como vimos con la llamada a subscribe()
, el navegador realizará solicitudes de red al servicio push para recuperar los detalles que conforman la PushSubscription.
Cada navegador usa un servicio push diferente, ¿no tienen APIs diferentes?
Todos los servicios push esperarán la misma API.
Esta API común se denomina protocolo de envío web y describe la solicitud de red que deberá hacer tu aplicación para activar un mensaje de envío.
Si suscribo a un usuario en su computadora de escritorio, ¿también se suscribe en su teléfono?
Lamentablemente, no. Un usuario debe registrarse para recibir notificaciones push en cada navegador en el que desee recibir mensajes. También es importante tener en cuenta que esto requerirá que el usuario otorgue permiso en cada dispositivo.
Próximos pasos
- Descripción general de las notificaciones push en la web
- Cómo funcionan las notificaciones push
- Cómo suscribir a un usuario
- UX de permisos
- Envía mensajes con bibliotecas push web
- Protocolo de envío web
- Cómo controlar los eventos push
- Cómo mostrar una notificación
- Comportamiento de las notificaciones
- Patrones de notificaciones comunes
- Preguntas frecuentes sobre las notificaciones push
- Problemas habituales y errores de informes