Inscrição de um usuário

A primeira etapa é conseguir a permissão do usuário para enviar mensagens push. Depois, podemos colocar as mãos em um PushSubscription.

A API JavaScript para fazer isso é razoavelmente simples. Confira o fluxo de lógica.

Detecção de recursos

Primeiro, precisamos verificar se o navegador atual oferece suporte a mensagens push. Podemos verificar se o push é compatível com duas verificações simples.

  1. Verifique se o serviceWorker está no navegador.
  2. Verifique se há PushManager na janela.
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;
}

Embora o suporte do navegador esteja crescendo rapidamente para o service worker e as mensagens push, é sempre uma boa ideia detectar recursos para ambos e melhorar progressivamente.

Registrar um service worker

Com a detecção de recursos, sabemos que os service workers e o push são compatíveis. A próxima etapa é "registrar" nosso service worker.

Ao registrar um service worker, informamos ao navegador onde está o arquivo dele. O arquivo ainda é apenas JavaScript, mas o navegador "dá acesso" às APIs do service worker, incluindo push. Para ser mais preciso, o navegador executa o arquivo em um ambiente de service worker.

Para registrar um service worker, chame navigator.serviceWorker.register(), transmitindo o caminho para nosso arquivo. Assim:

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

Essa função informa ao navegador que temos um arquivo de service worker e onde ele está localizado. Nesse caso, o arquivo do service worker está em /service-worker.js. Nos bastidores, o navegador vai seguir estas etapas após chamar register():

  1. Faça o download do arquivo do service worker.

  2. Execute o JavaScript.

  3. Se tudo funcionar corretamente e não houver erros, a promessa retornada por register() será resolvida. Se houver erros de qualquer tipo, a promessa será rejeitada.

Se o register() for rejeitado, verifique se há erros de digitação ou erros no JavaScript no Chrome DevTools.

Quando register() é resolvido, ele retorna um ServiceWorkerRegistration. Vamos usar esse registro para acessar a API PushManager.

Compatibilidade do navegador com a API PushManager

Compatibilidade com navegadores

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Origem

Solicitar permissão

Registramos nosso worker de serviço e estamos prontos para inscrever o usuário. A próxima etapa é receber a permissão do usuário para enviar mensagens push.

A API para receber permissão é relativamente simples. A desvantagem é que ela mudou recentemente de usar um callback para retornar uma promessa. O problema é que não podemos dizer qual versão da API é implementada pelo navegador atual. Portanto, você precisa implementar e processar as duas.

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

No código acima, o snippet importante é a chamada para Notification.requestPermission(). Esse método vai mostrar uma solicitação ao usuário:

Solicitação de permissão no Chrome para computador e dispositivo móvel.

Depois que o usuário interagir com o aviso de permissão pressionando "Permitir", "Bloquear" ou apenas fechando, o resultado será mostrado como uma string: 'granted', 'default' ou 'denied'.

No exemplo de código acima, a promessa retornada por askPermission() é resolvida se a permissão for concedida. Caso contrário, um erro é gerado, fazendo com que a promessa seja rejeitada.

Um caso extremo que você precisa processar é quando o usuário clica no botão "Bloquear". Se isso acontecer, seu app da Web não poderá pedir permissão ao usuário novamente. Eles vão precisar "desbloquear" o app manualmente, mudando o estado de permissão, que está oculto em um painel de configurações. Pense cuidadosamente sobre como e quando pedir permissão ao usuário, porque, se ele clicar em "Bloquear", não será fácil reverter essa decisão.

A boa notícia é que a maioria dos usuários aceita dar a permissão, desde que saibam por que ela está sendo solicitada.

Vamos analisar como alguns sites populares solicitam permissão mais tarde.

Inscrever um usuário com o PushManager

Depois de registrar o service worker e receber a permissão, podemos inscrever um usuário chamando 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;
    });
}

Ao chamar o método subscribe(), transmitimos um objeto options, que consiste em parâmetros obrigatórios e opcionais.

Vamos conferir todas as opções que podemos transmitir.

Opções de userVisibleOnly

Quando o push foi adicionado aos navegadores, havia incerteza sobre se os desenvolvedores poderiam enviar uma mensagem push e não mostrar uma notificação. Isso é comumente chamado de push silencioso, porque o usuário não sabe que algo aconteceu em segundo plano.

A preocupação era que os desenvolvedores pudessem fazer coisas desagradáveis, como rastrear a localização de um usuário de forma contínua sem que ele soubesse.

Para evitar esse cenário e dar tempo aos autores de especificações para considerar a melhor forma de oferecer suporte a esse recurso, a opção userVisibleOnly foi adicionada. Transmitir um valor de true é um acordo simbólico com o navegador de que o app da Web vai mostrar uma notificação sempre que um push for recebido (ou seja, não há push silencioso).

No momento, você precisa transmitir um valor de true. Se você não incluir a chave userVisibleOnly ou transmiti-la em false, vai receber o seguinte erro:

No momento, o Chrome só oferece suporte à API Push para assinaturas que resultam em mensagens visíveis para o usuário. Para indicar isso, chame pushManager.subscribe({userVisibleOnly: true}). Consulte https://goo.gl/yqv4Q4 para mais detalhes.

Parece que o push silencioso geral nunca será implementado no Chrome. Em vez disso, os autores de especificações estão explorando a noção de uma API de orçamento, que permite que os apps da Web tenham um determinado número de mensagens push silenciosas com base no uso de um app da Web.

Opção applicationServerKey

Mencionamos brevemente as "chaves do servidor do aplicativo" na seção anterior. As "chaves do servidor do aplicativo" são usadas por um serviço push para identificar o aplicativo que está assinando um usuário e garantir que o mesmo aplicativo esteja enviando mensagens para esse usuário.

As chaves do servidor do aplicativo são um par de chaves públicas e privadas exclusivo para o aplicativo. A chave privada precisa ser mantida em segredo para seu aplicativo, e a chave pública pode ser compartilhada livremente.

A opção applicationServerKey transmitida para a chamada subscribe() é a chave pública do aplicativo. O navegador transmite isso a um serviço push ao inscrever o usuário. Isso significa que o serviço push pode vincular a chave pública do aplicativo ao PushSubscription do usuário.

O diagrama abaixo ilustra essas etapas.

  1. O app da Web é carregado em um navegador e você chama subscribe(), transmitindo sua chave pública do servidor do aplicativo.
  2. Em seguida, o navegador faz uma solicitação de rede para um serviço push, que gera um endpoint, associa esse endpoint à chave pública do aplicativo e o retorna ao navegador.
  3. O navegador vai adicionar esse endpoint ao PushSubscription, que é retornado pela promessa subscribe().

Ilustração da chave pública do servidor do aplicativo usada no método
de inscrição.

Quando quiser enviar uma mensagem push mais tarde, crie um cabeçalho Authorization, que vai conter informações assinadas com a chave privada do servidor do aplicativo. Quando o serviço de push recebe uma solicitação para enviar uma mensagem de push, ele pode validar este cabeçalho Autorização assinado procurando a chave pública vinculada ao endpoint que recebe a solicitação. Se a assinatura for válida, o serviço de push saberá que ela deve ter vindo do servidor do aplicativo com a chave privada correspondente. Basicamente, é uma medida de segurança que impede que outras pessoas enviem mensagens para os usuários de um app.

Como a chave privada do servidor do aplicativo é usada ao enviar uma
mensagem

Tecnicamente, o applicationServerKey é opcional. No entanto, a implementação mais fácil no Chrome exige isso, e outros navegadores podem exigir isso no futuro. Ele é opcional no Firefox.

A especificação que define o que a chave do servidor de aplicativos precisa ser é a especificação do VAPID. Sempre que você ler algo que se refira a "chaves do servidor de aplicativos" ou "chaves VAPID", lembre-se de que elas são a mesma coisa.

Como criar chaves de servidor de aplicativos

Para criar um conjunto público e privado de chaves do servidor de aplicativos, acesse web-push-codelab.glitch.me ou use a linha de comando web-push para gerar chaves:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

Você só precisa criar essas chaves uma vez para seu aplicativo. Basta manter a chave privada privada. (Sim, eu disse isso.)

Permissões e subscribe()

Há um efeito colateral de chamar subscribe(). Se o app da Web não tiver permissões para mostrar notificações no momento da chamada de subscribe(), o navegador vai solicitar as permissões para você. Isso é útil se a interface funcionar com esse fluxo, mas, se você quiser mais controle (e acho que a maioria dos desenvolvedores vai querer), use a API Notification.requestPermission() que usamos anteriormente.

O que é uma PushSubscription?

Chamamos subscribe(), transmitimos algumas opções e, em troca, recebemos uma promessa que é resolvida em um PushSubscription, resultando em um código como este:

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

O objeto PushSubscription contém todas as informações necessárias para enviar uma mensagem push a esse usuário. Se você imprimir o conteúdo usando JSON.stringify(), verá o seguinte:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

O endpoint é o URL dos serviços push. Para acionar uma mensagem push, faça uma solicitação POST para este URL.

O objeto keys contém os valores usados para criptografar os dados da mensagem enviados com uma mensagem push, que será abordada mais adiante nesta seção.

Renovação regular para evitar a expiração

Ao assinar as notificações push, você geralmente recebe uma PushSubscription.expirationTime de null. Em teoria, isso significa que a assinatura nunca expira (ao contrário de quando você recebe um DOMHighResTimeStamp, que informa o momento exato em que a assinatura expira). Na prática, no entanto, é comum que os navegadores ainda permitam que as assinaturas expirem, por exemplo, se nenhuma notificação push for recebida por um período mais longo ou se o navegador detectar que o usuário não está usando um app que tenha a permissão de notificações push. Um padrão para evitar isso é fazer a inscrição do usuário novamente a cada notificação recebida, conforme mostrado no snippet abaixo. Isso exige que você envie notificações com frequência suficiente para que o navegador não tenha expirado a assinatura automaticamente. É preciso pesar cuidadosamente as vantagens e desvantagens das necessidades de notificação legítimas em relação ao envio involuntário de spam para o usuário apenas para que a assinatura não expire. No final, não tente lutar contra o navegador nos esforços para proteger o usuário de assinaturas de notificação esquecidas.

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

Enviar uma assinatura para seu servidor

Depois de ter uma assinatura push, você vai querer enviá-la ao servidor. Cabe a você decidir como fazer isso, mas uma dica é usar JSON.stringify() para extrair todos os dados necessários do objeto de assinatura. Como alternativa, você pode reunir o mesmo resultado manualmente, assim:

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

O envio da assinatura é feito na página da Web desta forma:

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

O servidor de nó recebe essa solicitação e salva os dados em um banco de dados para uso posterior.

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.',
          },
        }),
      );
    });
});

Com os detalhes PushSubscription no servidor, podemos enviar uma mensagem ao usuário sempre que quisermos.

Renovação regular para evitar a expiração

Ao assinar as notificações push, você geralmente recebe uma PushSubscription.expirationTime de null. Em teoria, isso significa que a assinatura nunca expira (ao contrário de quando você recebe um DOMHighResTimeStamp, que informa o momento exato em que a assinatura expira). Na prática, no entanto, é comum que os navegadores ainda permitam que as assinaturas expirem, por exemplo, se nenhuma notificação por push for recebida por um longo período ou se o navegador detectar que o usuário não está usando o app que tem a permissão de notificações por push. Um padrão para evitar isso é fazer a inscrição do usuário novamente a cada notificação recebida, conforme mostrado no snippet abaixo. Isso exige que você envie notificações com frequência suficiente para que o navegador não tenha expirado a assinatura automaticamente. É preciso pesar cuidadosamente as vantagens e desvantagens das necessidades de notificação legítimas em relação ao envio de spam para o usuário apenas para que a assinatura não expire. No final, não tente lutar contra o navegador nos esforços para proteger o usuário de assinaturas de notificação esquecidas.

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

Perguntas frequentes

Algumas perguntas comuns que as pessoas fazem neste momento:

Posso mudar o serviço push que um navegador usa?

Não. O serviço de push é selecionado pelo navegador e, como vimos com a chamada subscribe(), o navegador faz solicitações de rede para o serviço de push para recuperar os detalhes que compõem a PushSubscription.

Cada navegador usa um serviço de push diferente. Eles não têm APIs diferentes?

Todos os serviços de push vão esperar a mesma API.

Essa API comum é chamada de Protocolo Push da Web e descreve a solicitação de rede que seu aplicativo precisa fazer para acionar uma mensagem push.

Se eu assinar um usuário no computador, ele também vai estar inscrito no smartphone?

Infelizmente não. O usuário precisa se registrar para receber mensagens push em cada navegador. Vale lembrar que isso vai exigir que o usuário conceda permissão em cada dispositivo.

A seguir

Code labs