- Push API (W3C)
- Mozilla Autopush
- Web Push Libs
- Web Push
- Google Android
- Google Dev
- Tizen Dev.
- Facebook Push/MQTT (medium)
Last active
December 27, 2017 19:37
-
-
Save subversivo58/d6b4b6ec9af77cff996c40d927a61113 to your computer and use it in GitHub Desktop.
Response to: https://pt.stackoverflow.com/questions/265112
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @see font: https://developer.android.com/guide/topics/ui/notifiers/notifications.html | |
*/ | |
NotificationCompat.Builder mBuilder = | |
new NotificationCompat.Builder(this) | |
.setSmallIcon(R.drawable.notification_icon) | |
.setContentTitle("My notification") | |
.setContentText("Hello World!"); | |
// Creates an explicit intent for an Activity in your app | |
Intent resultIntent = new Intent(this, ResultActivity.class); | |
// The stack builder object will contain an artificial back stack for the | |
// started Activity. | |
// This ensures that navigating backward from the Activity leads out of | |
// your application to the Home screen. | |
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); | |
// Adds the back stack for the Intent (but not the Intent itself) | |
stackBuilder.addParentStack(ResultActivity.class); | |
// Adds the Intent that starts the Activity to the top of the stack | |
stackBuilder.addNextIntent(resultIntent); | |
PendingIntent resultPendingIntent = | |
stackBuilder.getPendingIntent( | |
0, | |
PendingIntent.FLAG_UPDATE_CURRENT | |
); | |
mBuilder.setContentIntent(resultPendingIntent); | |
NotificationManager mNotificationManager = | |
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); | |
// mId allows you to update the notification later on. | |
mNotificationManager.notify(mId, mBuilder.build()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @see font: https://support.insert.io/hc/en-us/articles/209724505-Send-push-notifications-via-Insert-Android- | |
*/ | |
/** | |
* use "Insert" with Firebase | |
*/ | |
public class InsertFirebaseMessagingService extends FirebaseMessagingService { | |
@Override | |
public void onMessageReceived(RemoteMessage remoteMessage) { | |
if ( remoteMessage.getNotification() != null ) { | |
RemoteMessage.Notification notification = remoteMessage.getNotification(); | |
// Icon | |
int iconResId = 0; | |
final String drawableResourceName = notification.getIcon(); | |
if ( !TextUtils.isEmpty(drawableResourceName) ) { | |
iconResId = getResources().getIdentifier(drawableResourceName, "drawable", getPackageName()); | |
} | |
// Sound | |
Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); | |
final String soundFileName = notification.getSound(); | |
if ( !TextUtils.isEmpty(soundFileName) && !soundFileName.equals("default") ) { | |
sound = Uri.parse("android.resource://" + this.getPackageName() + "/raw/" + soundFileName); | |
} | |
Bundle bundle = new Bundle(); | |
for (Map.Entry<String, String> entry : remoteMessage.getData().entrySet()) { | |
bundle.putString(entry.getKey(), entry.getValue()); | |
} | |
// Build Notification | |
NotificationCompat.Builder builder = new NotificationCompat.Builder(this) | |
.setContentTitle("Your App Name") | |
.setContentText(notification.getBody()) | |
.setAutoCancel(true) | |
.setSmallIcon(iconResId == 0 ? getApplicationInfo().icon : iconResId) | |
.setSound(sound) | |
.setContentIntent(Insert.generatePendingIntentForPush(bundle)); | |
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); | |
nm.notify(0, builder.build()); | |
} | |
} | |
} | |
/** | |
* use "Insert" with GCM | |
*/ | |
// registrartion Intent service | |
public class RegistrationIntentService extends IntentService { | |
public RegistrationIntentService() { | |
super("RegistrationIntentService"); | |
} | |
@Override | |
protected void onHandleIntent(Intent intent) { | |
try { | |
// Initially this call goes out to the network to retrieve the token, subsequent calls are local. | |
// R.string.gcm_defaultSenderId (the Sender ID) is derived from google-services.json (the project number). | |
InstanceID instanceID = InstanceID.getInstance(this); | |
String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); | |
Insert.setPushId(token); | |
} catch (Exception e) {} | |
} | |
} | |
// start the registration service from the main activity | |
Intent intent = new Intent(this, RegistrationIntentService.class); | |
startService(intent); | |
// Instance ID listener service | |
public class InsertInstanceIDListenerService extends InstanceIDListenerService { | |
@Override | |
public void onTokenRefresh() { | |
Intent intent = new Intent(this, RegistrationIntentService.class); | |
startService(intent); | |
} | |
} | |
// Listener service to display incoming notification | |
public class InsertGcmListenerService extends GcmListenerService { | |
@Override | |
public void onMessageReceived(String from, final Bundle data) { | |
// Icon | |
int iconResId = 0; | |
final String drawableResourceName = data.getBundle("notification").getString("icon"); | |
if ( !TextUtils.isEmpty(drawableResourceName) ) { | |
iconResId = getResources().getIdentifier(drawableResourceName, "drawable", getPackageName()); | |
} | |
// Sound | |
Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); | |
final String soundFileName = data.getBundle("notification").getString("sound"); | |
if ( !TextUtils.isEmpty(soundFileName) && !soundFileName.equals("default") ) { | |
sound = Uri.parse("android.resource://" + this.getPackageName() + "/raw/" + soundFileName); | |
} | |
// Build Notification | |
NotificationCompat.Builder builder = new NotificationCompat.Builder(this) | |
.setContentTitle("Your App Name") | |
.setContentText(data.getString("message")) | |
.setAutoCancel(true) | |
.setSmallIcon(iconResId == 0 ? getApplicationInfo().icon : iconResId) | |
.setSound(sound) | |
.setContentIntent(Insert.generatePendingIntentForPush(data)); | |
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); | |
nm.notify(0, builder.build()); | |
} | |
} | |
/** | |
* use "Insert" with Cordova (Push Notification plugin) | |
*/ | |
// Cordova command line: | |
cordova plugin add phonegap-plugin-push --variable SENDER_ID=<sender-id> | |
// Add the following code after "onDeviceReady" | |
var push = PushNotification.init({ | |
"android": { | |
"senderID": "<sender-id>" | |
}, | |
"ios": { | |
"alert": "true", | |
"badge": "true", | |
"sound": "true" | |
}, | |
"windows": {} | |
}); | |
push.on('registration', function(data) { | |
window.plugins.Insert.setPushId(data.registrationId); | |
}); | |
push.on('notification', function(data) { | |
// this function will be called when the push notification is received by the app | |
}); | |
push.on('error', function(e) { | |
console.log(e.message); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<DOCTYPE html> | |
<html lang="en-US"> | |
<head> | |
<charset="UTF-8"> | |
<title>Web Pus Test</title> | |
<!-- Bootstrap 4 beta 2 --> | |
<link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"> | |
</head> | |
<body> | |
<section class="subscription-details js-subscription-details is-invisible"> | |
<p>Uma vez que você inscreveu seu usuário, você enviará esta inscrição para o seu servidor para armazenar em um banco de dados para que, quando você desejar enviar uma mensagem, você pode pesquisar pela assinatura e enviar uma mensagem para ela.</p> | |
<pre><code class="js-subscription-json"></code></pre> | |
</section> | |
<button disabled class="js-push-btn btn btn-info rounded-0"> | |
Enable Push Messaging | |
</button> | |
<!-- JavaScript --> | |
<script> | |
'use strict'; | |
// VAPID public key | |
const applicationServerPublicKey = 'BHx3slN8kpDixb_mCc7QgzZ4MvkvFCIZvfEiWt3DGb2QL2ACTDPdJItVAarXMnrKLk2H8Qp-AFFVW2uQT6TcjoA'; | |
// referenciar o botão | |
const pushButton = document.querySelector('.js-push-btn'); | |
// pré-armazenar uma variável que define o status da subscrição | |
let isSubscribed = false; | |
// pré-armazenar uma variável para o objeto do registro | |
let swRegistration = null; | |
// converter base64URL em UInt8Array | |
function urlB64ToUint8Array(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; | |
} | |
// atualizar texto no botão | |
function updateBtn() { | |
// verificar se a permissão do Navegadro para usar Notificações foi negada | |
if ( Notification.permission === 'denied' ) { | |
// atualizar botão | |
pushButton.textContent = 'Push Messaging Blocked.'; | |
// desativar o botão | |
pushButton.disabled = true; | |
// mostrar objeto da subscrição | |
showSubscriptionObject(null); | |
return; | |
} | |
// mudar o botão conforme o status da subscriçao | |
if ( isSubscribed ) { | |
pushButton.textContent = 'Disable Push Messaging'; | |
} else { | |
pushButton.textContent = 'Enable Push Messaging'; | |
} | |
// reativar o botão | |
pushButton.disabled = false; | |
} | |
/** | |
Para este exemplo esta função mostra no (adiciona ao) HTML em um bloco de código | |
o objeto da inscrição (subscrição) | |
*/ | |
function showSubscriptionObject(subscription) { | |
// referenciar elementos | |
const subscriptionJson = document.querySelector('.js-subscription-json'); | |
// verificar se existe um objeto da subscrição | |
if ( subscription ) { | |
subscriptionJson.textContent = JSON.stringify(subscription, null, 4); | |
} | |
} | |
// inscrever o usuário | |
function subscribeUser() { | |
swRegistration.pushManager.subscribe({ | |
// obrigatório... declara que vai mostrar uma notificação sempre que manipular um evento push | |
userVisibleOnly: true, | |
// credencias | |
applicationServerKey: urlB64ToUint8Array(applicationServerPublicKey) | |
}).then(function(subscription) { | |
// enviar para o servidor | |
fetch('/push/subscribe', { | |
method: 'post', | |
mode: 'cors', | |
referrerPolicy: 'origin', | |
credentials: 'include', | |
headers: { | |
'Accept': 'application/json', | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(subscription) | |
}).then(function(response) { | |
return response; | |
}).then(function(text) { | |
console.log('User is subscribed.'); | |
// mostrar objeto da subscrição | |
showSubscriptionObject(subscription); | |
// redefinir o status da subscrição | |
isSubscribed = true; | |
// atualizar o botão | |
updateBtn(); | |
}).catch(function(error) { | |
console.log('Failed to subscribe the user: ', err); | |
// atualizar o botão | |
updateBtn(); | |
}); | |
}).catch(function(err) { | |
console.log('Failed to subscribe the user: ', err); | |
}); | |
} | |
// remover inscrição | |
function unsubscribeUser(){ | |
swRegistration.pushManager.getSubscription().then(function(subscription) { | |
// verificar se há um objeto da subscrição | |
if ( subscription ) { | |
// enviar para o servidor | |
fetch('/push/unsubscribe', { | |
method: "POST", | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(subscription) | |
}).then(function(response) { | |
if ( response.ok ) { | |
return response.json(); | |
} | |
throw new Error('Failed unsubscrible Push Notifications! Return server code: ' + response.status); | |
}).then(function(json) { | |
// mostrar objeto da subscrição | |
showSubscriptionObject(null); | |
console.log('User is unsubscribed.'); | |
// redefinir o status da subscrição | |
isSubscribed = false; | |
// atualizar o botão | |
updateBtn(); | |
// remover inscrição do objeto... retorna uma Promessa e em caso de erro pode ser caputurado pelo "catch" | |
subscription.unsubscribe(); | |
}).catch(function(error) { | |
console.log('Error unsubscribing', error.message); | |
}); | |
} | |
}); | |
} | |
function initialiseUI() { | |
// observar o evento "click" no botão | |
pushButton.addEventListener('click', function() { | |
// desativar o botão | |
pushButton.disabled = true; | |
if ( isSubscribed ) { | |
// se o usuário já está inscrito... remover inscrição | |
unsubscribeUser(); | |
} else { | |
// inscrever o usuário | |
subscribeUser(); | |
} | |
}); | |
// obter o status da subscrição | |
swRegistration.pushManager.getSubscription().then(function(subscription) { | |
// sobreescrever (ou não) o status da subscrição | |
isSubscribed = !(subscription === null); | |
// mostrar objeto da subscrição | |
showSubscriptionObject(subscription); | |
if ( isSubscribed ) { | |
console.log('User IS subscribed.'); | |
} else { | |
console.log('User is NOT subscribed.'); | |
} | |
// atualizar botão | |
updateBtn(); | |
}); | |
} | |
// verificar se o Navegador oferece supporte a Service Worker | |
if ( 'serviceWorker' in navigator && 'PushManager' in window ) { | |
// inicializar o Service Worker | |
navigator.serviceWorker.register('./sw.js').then(function(swReg) { | |
// reescrever a variável para deixar o objeto do registro acessível "globalmente" | |
swRegistration = swReg; | |
// ajustar a UI | |
initialiseUI(); | |
}).catch(function(error){ | |
console.error('Service Worker Error', error); | |
}); | |
} else { | |
console.warn('Push messaging is not supported'); | |
pushButton.textContent = 'Push Not Supported'; | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* as rotas são acessadas pelo método POST e devem conter no minimo 2 campos: | |
-- endpoint {String} | |
-- keys {Object} | |
"keys" por sua vez deve conter duas propriedades: | |
-- p256dh: {String} | |
-- auth: {String} | |
*/ | |
const express = require('express') | |
const router = express.Router() | |
// assumindo que a chave VAPID já foi gerada e esta armazenada por exemplo nas variaveis | |
webpush.setVapidDetails( | |
'mailto:[email protected]', // fallback para o qual os serviços podem (ou não) reportar uma falha na entrega da mensagem. | |
process.env.PUSH_PUB, // VAPID chave publica | |
process.env.PUSH_PRI // VAPID chave privada | |
) | |
// rota de inscrição | |
router.post('push/subscribe', (req, res, next) => { | |
// | |
const subscription = { | |
endpoint: req.body.endpoint, | |
keys: { | |
p256dh: req.body.keys.p256dh, | |
auth: req.body.keys.auth | |
} | |
} | |
// para utilizar o p256dh como um identificador (ao salvar no bando de dados por exemplo) | |
let pushId = subscription.keys.p256dh.replace(/-/g,'').replace(/=/g,'') | |
// supondo que a aplicação ira salvar esta chave e no caso de sucesso ira então enviar uma notificação... | |
// definir o payload | |
let payload = JSON.stringify({ | |
title: 'Um titulo legal', | |
body: 'Alguma coisa aqui...', | |
icon: 'imagem.png' // caminho absoluto ou relativo (relativo ao servidor) | |
}) | |
// definir um TTL em segundos | |
let options = { | |
TTL: 60 // 1 minuto | |
} | |
// enviar... | |
webpush.sendNotification(subscription, payload, options).then(response => { | |
// tratar o successo | |
}).catch(e => { | |
// tratar o erro | |
}) | |
}) | |
// rota para remover inscrição | |
router.post('/push/unsubscribe', (req, res, next) => { | |
// limpar a chave p256dh para procurar no banco de dados | |
let pushId = req.body.keys.p256dh.replace(/-/g,'').replace(/=/g,'') | |
// após executar a tarefa de remoção devolver uma resposta ao front-end: | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// VAPID public key | |
const applicationServerPublicKey = 'BKXyMNOcPJMEfNnYWUrErN86WCacx4jdfepDR23x-cHkLP7TUj2cZ6Sp_UFRHFZYSfx7-Bk4XJHWcPgGi7DaASc'; | |
// uma simples função para "estender" objetos | |
function extend() { | |
try { | |
let hasOwnProperty = Object.prototype.hasOwnProperty, | |
target = {} | |
for (let i = 0; i < arguments.length; i++) { | |
let source = arguments[i] | |
for (let key in source) { | |
if ( hasOwnProperty.call(source, key) ) { | |
target[key] = source[key] | |
} | |
} | |
} | |
return target | |
} catch(e) { | |
return {} | |
} | |
} | |
// converter base64URL em UInt8Array | |
function urlB64ToUint8Array(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; | |
} | |
// observar quando uma notificação chegar | |
self.addEventListener('push', function(event){ | |
console.log('[Service Worker] Push Received.'); | |
// definir propriedades de fallback caso ocorra erro ao resgatar a mensagem | |
let defautOptions = { | |
title: 'Site Name', | |
body: 'Mensagem padrão', | |
// imagens podem ter urls relativas ou absolutas | |
icon: './img/icons/icon.png', | |
badge: './img/icons/icon-badage.png', | |
//image: './img/push-banner-1600x1100.jpg', // imagem banner | |
//vibrate: [200, 100, 200], // caso seja um dispositivo mobile | |
//sound: './media/audio/incoming.m4a', // caso seja um dispositivo mobile | |
dir: 'auto', // direção do texto: auto, ltr (esquerda para direita), rtl (direita para esquerda) | |
tag: 'default', // define se uma mensagem é única ou se será enfileirada em grupo | |
requireInteraction: false, // "true" obriga uma iteração (para desktop) | |
renotify: false, // "true" requer obrigátoriamente uma etiqueta (tag) | |
silent: false // "true" não vibra, não dispara som nem dispara o led luminoso | |
} | |
// obter o payload | |
var data = event.data.text() | |
// utilizar o bloco try/catch para tratar errors | |
try { | |
// transformar o payload em um {Object} JavaScript | |
let object = JSON.parse(data) | |
// verificar se há a propriedade data {Object} | |
if ( 'data' in object ) { | |
//... | |
} | |
// add timestamp | |
object.timestamp = new Date().getTime() | |
// redefinir a variável "data" | |
data = object | |
} catch(ex) { | |
// redefinir a variável "data" | |
data = {}; | |
// extender usando as o~pções padrão | |
data = extend(data, defaultOptions) | |
} | |
// disparar a notificação | |
event.waitUntil(self.registration.showNotification(data.title, data)); | |
}); | |
// observar o evento click na notificação | |
self.addEventListener('notificationclick', function(event){ | |
console.log('[Service Worker] Notification click Received.'); | |
// close notification | |
event.notification.close(); | |
// verificar se há conteúdo | |
if ( Object.keys(event.notification).length > 0 ) { | |
// verificar se há a propriedade "data" | |
if ( 'data' in event.notification ) { | |
let data = event.notification.data; | |
/** | |
Como é possível adicionar uma propriedade "data" ao payload e esta pode conter valores arbitrários... | |
é possível adicionar urls para abrir no evento click ou mesmos executar outras instruções | |
definidas em seu Service Worker. | |
Exemplo: | |
if ( data.openWindow ) { | |
event.waitUntil( | |
clients.openWindow(data.url) | |
); | |
} | |
*/ | |
} | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
esta etapa deve ser executada apenas uma vez visto que, o navegador | |
do usuário irá utilizá-la para se registrar no "serviço" e se a chave | |
for alterada será necessário recadastrar o navegador do usuário... | |
como isto é tratado pelo front-end através de uma iteração do usuário, | |
usuários cadastrados com uma chave antiga não receberão notificações | |
disparadas usando uma chave nova | |
*/ | |
// web-push biblioteca | |
const webpush = require('web-push') | |
// criar chaves | |
const vapidKeys = webpush.generateVAPIDKeys() | |
// um simples exemplo mostrando para o console (você deve salvar o par de chaves no banco de dados ou em um arquivo) | |
console.log({ | |
pub: vapidKeys.publicKey, | |
pri: vapidKeys.privateKey | |
}) | |
// a saída irá gerar algo como: | |
{ | |
pub: 'BKXyMNOcPJMEfNnYWUrErN86WCacx4jdfepDR23x-cHkLP7TUj2cZ6Sp_UFRHFZYSfx7-Bk4XJHWcPgGi7DaASc', | |
pri: 'sXwt3VYs_XKe0ST8fwH5CkBzdaT6mXuUlRImcJQAWak' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment