Preface¹:
First it should be understood that the services of Push Notification in the mobile (mobile) scope are implemented by the software manufacturer (normally integrated via OS).
The basis of its operation is the "Operating System Sending Notification Service" (OSPNS). Each mobile operating system (OS), including iOS, Android, Firefox OS, Windows, Blackberry and others have their own service. Browsers use the "endpoint" of these services (their own) to support API
Web Push
as described below.
The methodology of its implementation follows the concept Pub/Sub (subscription/publication) where, the device subscribes to a service (server) informing a id exclusive to the application and then this whistle to receive a publication (notification). To this end, the device "constantly observes" the service in order to verify that there is (exists) a new publication (notification) to be delivered.
As mentioned above these services (servers) are "embedded" by the software manufacturer and the method as well as the verification frequency are defined by the manufacturers.
The example of this we can list for example:
- Apple APNS (Apple Push Notification Service) (iOS/Mac OS X)
- Google GCM/FCM (Google Cloud Messaging/Firebase Cloud Messaging) (Android)
- Mozila Autopush (Mozilla Push server and Push Endpoint) (Firefox/Firefox OS)
- Windows WNS (Windows Push Notification Services) (UWP)
- Microsoft MPNS (Microsoft Push Notification Service) (Windows Phone 8)
- Amazon ADM (Amazon Device Messaging) (by SNS)
- Baidu Cloud Push (Baidu Cloud Push SDK)
- Tizen SPPCD² (Samsung Push Service) (Tizen OS)
NOTES:
like the above APNS list can be used both on the device (iOS/Mac OS X) and on a watchOS 3 or later... the system decides which device received the notification. See more details on official documentation.
Autopush is used in Mozila’s mobile system as well as to meet the specification Web Push in web applications using Firefox. See more details on official documentation.
Windows WNS is used in UWP (Universal Windows Platform) that integrates the connected services of the tenth (10) version of this OS.
Other mobile OS derivatives (for example Android) may use the services from their source base or provide their own service.
Implement a service of its own:
At the time of this publication (12/27/2017) I do not know the possibility of implementing a service for native use of push notifications on a device from a remote source.
The use of native functions to push requires registration in a service (until then proprietary), it is not possible to fire them locally (on the device) without being previously registered (from a registered origin).
Alternatives:
There are many alternatives (push application servers), some free other paid which in short will use some of the services mentioned above.
These tools (push application servers) in turn use plugins or tools integrated into the SDK
the system in question to extend native classes and functionalities relating to "push" in order to handle the requisitions (referred to as REST) of these services.
The big difference is that these tools provide some degree of automation (for example for marketing) as well as the possibility of management tools and analysis which by default is restricted to bad software manufacturers, how the message is encrypted these services (owners) function as a gatekeeper based on the TTL
(time-to-live) and the message priority.
These tools also enable you to use a remote access (for example a web application server or via command line) to launch new notifications or in some cases get metrics (usage analysis data) via API
.
By following the reasoning of these "tools" your application could maintain a connection to your server and launch a notification using "Insert [Android]"... this method would necessarily require a API Key
GCM and could be used either by GCM or FCM or even by the Cordova plugin (in the case of a Cordova application).
example using "Insert" - (see commented code gist):
/* Firebase instruções */
public class InsertFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
if ( remoteMessage.getNotification() != null ) {
RemoteMessage.Notification notification = remoteMessage.getNotification();
int iconResId = 0;
final String drawableResourceName = notification.getIcon();
if ( !TextUtils.isEmpty(drawableResourceName) ) {
iconResId = getResources().getIdentifier(drawableResourceName, "drawable", getPackageName());
}
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());
}
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());
}
}
}
/* GCM instruções */
public class RegistrationIntentService extends IntentService {
public RegistrationIntentService() {
super("RegistrationIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
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) {}
}
}
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
public class InsertInstanceIDListenerService extends InstanceIDListenerService {
@Override
public void onTokenRefresh() {
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
}
public class InsertGcmListenerService extends GcmListenerService {
@Override
public void onMessageReceived(String from, final Bundle data) {
int iconResId = 0;
final String drawableResourceName = data.getBundle("notification").getString("icon");
if ( !TextUtils.isEmpty(drawableResourceName) ) {
iconResId = getResources().getIdentifier(drawableResourceName, "drawable", getPackageName());
}
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);
}
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());
}
}
/* Cordova (Push Notification plugin) instruções */
// 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) {
//...
});
push.on('error', function(e) {
console.log(e.message);
});
Source: support.insertio.
If the intention is simply to notify the user your application (mobile) could communicate with a remote server through the use of WebSockets
or from a third party library and use a native notification function... "but this would no longer be a push notification"!
example (Android) - (see commented code gist)
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!");
Intent resultIntent = new Intent(this, ResultActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(ResultActivity.class);
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(mId, mBuilder.build());
Source: Developer.android.com
The example of this Facebook Messenger uses on Android and iOS its own gateway
using MQTT to send and receive notifications.
Say on iOS, since apps can’t run services in the background for long, they use Apns when the application is closed and MQTT when opened.
Source: IBM.
There is also the possibility to create a "push application server"...
Push application servers:
There is a considerably good range (opinion poll) of open source projects that can serve to create your own push app service the following listing reflects some:
- rpush
- written in Ruby
- support for: APNS, FCM, ADM, WPNS
- license: MIT
- gorush
- Written in: Go
- supports: APNS, FCM
- license: MIT
- pushd
- Written in: Coffeescript
- support for: APNS, GCM, WNS, MPNS, HTTP (POST), Eventsource
- license: MIT
- uniqush-push
- Written in: Go
- support for: APNS, GCM, ADM
- license: Apache-2.0
For specific use in Android your application (configured to Push) using the FCM credentials would follow the following scheme:
Related: Web Push
To API
Web Push is currently in the cycle WD (draft) according to the W3C, this API
is designed to bring the functionality of services to web applications "Push Notification" and second caniuse with. on the current date (12/26/2017) is supported in the following Android system mobile browsers (in addition to desktop browsers):
- Opera Mobile (37)
- Chrome for Android (62)
- Firefox for Android (57)
- UC for Android (11.4)
- Samsung Internet (62)
- QQ Browser (1.2)
To create a "push application server" using Web Push there is a large offer of Open Source projects among which I would indicate the following repository that contains a collection of sources:
- web-push-libs - this collection contains sources in:
- C, PHP, Javascript, Python, C# and Java (constantly updated)
However this API
has its "peculiarities" as the (mandatory) use of API
Service Worker as well as user permissions to use the notifications browser.
Its specification requires the server to provide a key VAPID
(Voluntary Application Server Identification) as described in RFC8292.
For a brief understanding I take as reference the use of the library web-push written in JavaScript
to rotate in Nodejs... this library follows the Web Push protocol as well as the RFC8030 to encrypt messages from this API
in addition to, contain a method for generating keys VAPID
(useful because it saves work).
example (chronology):
1- generate a key pair VAPID
of your server
2- create "subscription/unsubscribe" routes (using routes Express.js)
3- add a Service Worker
4- require the user to use notifications (and have approval)
5- test (register/remove record)
code example (generate VAPID keys) - (see commented code gist):
const webpush = require('web-push')
const vapidKeys = webpush.generateVAPIDKeys()
console.log({
pub: vapidKeys.publicKey,
pri: vapidKeys.privateKey
})
Both keys (public/private) are used on the server while the public key is used on the Service Worker we’ll see later on.
code example (creating routes) - (see commented code gist):
const express = require('express')
const router = express.Router()
webpush.setVapidDetails(
'mailto:[email protected]',
process.env.PUSH_PUB,
process.env.PUSH_PRI
)
router.post('push/subscribe', (req, res, next) => {
const subscription = {
endpoint: req.body.endpoint,
keys: {
p256dh: req.body.keys.p256dh,
auth: req.body.keys.auth
}
}
let pushId = subscription.keys.p256dh.replace(/-/g,'').replace(/=/g,'')
let payload = JSON.stringify({
title: 'Um titulo legal',
body: 'Alguma coisa aqui...',
icon: 'imagem.png'
})
let options = {
TTL: 60
}
webpush.sendNotification(subscription, payload, options).then(response => {
//...
}).catch(e => {
//...
})
})
router.post('/push/unsubscribe', (req, res, next) => {
let pushId = req.body.keys.p256dh.replace(/-/g,'').replace(/=/g,'')
//...
})
There is no standard response for the front-end this is at the discretion of the developer... good tips would be:
- for registration status:
- 500 for a "generalized" error (not described)
- 406 if requirements are not accepted (Not Acceptable)
- 201 for a record successfully completed (Created)
- to remove registration status:
- 500 for a "generalized" error (not described)
- 404 if you don’t find (no) a registered key (Not Found)
- 406 if requirements are not accepted (Not Acceptable)
- 200 for a successfully performed removal (OK)
If the system is used only for users registered in the application the status 403
(Forbidden) for attempts to access by non-logged-in users.
example Service Worker (sw.js) - (see commented code gist):
const applicationServerPublicKey = 'BKXyMNOcPJMEfNnYWUrErN86WCacx4jdfepDR23x-cHkLP7TUj2cZ6Sp_UFRHFZYSfx7-Bk4XJHWcPgGi7DaASc';
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 {}
}
}
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;
}
self.addEventListener('push', function(event){
console.log('[Service Worker] Push Received.');
let defautOptions = {
title: 'Site Name',
body: 'Mensagem padrão',
icon: './img/icons/icon.png',
badge: './img/icons/icon-badage.png',
//image: './img/push-banner-1600x1100.jpg',
//vibrate: [200, 100, 200],
//sound: './media/audio/incoming.m4a',
dir: 'auto',
tag: 'default',
requireInteraction: false,
renotify: false,
silent: false
}
var data = event.data.text()
try {
let object = JSON.parse(data)
if ( 'data' in object ) {
//...
}
object.timestamp = new Date().getTime()
data = object
} catch(ex) {
data = {};
data = extend(data, defaultOptions)
}
event.waitUntil(self.registration.showNotification(data.title, data));
});
self.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
event.notification.close();
if ( Object.keys(event.notification).length > 0 ) {
if ( 'data' in event.notification ) {
let data = event.notification.data;
}
}
});
code example (index.html) - (see commented code gist):
<DOCTYPE html>
<html lang="en-US">
<head>
<charset="UTF-8">
<title>Web Pus Test</title>
<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>
<script>
'use strict';
const applicationServerPublicKey = 'BKXyMNOcPJMEfNnYWUrErN86WCacx4jdfepDR23x-cHkLP7TUj2cZ6Sp_UFRHFZYSfx7-Bk4XJHWcPgGi7DaASc';
const pushButton = document.querySelector('.js-push-btn');
let isSubscribed = false;
let swRegistration = null;
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;
}
function updateBtn() {
if ( Notification.permission === 'denied' ) {
pushButton.textContent = 'Push Messaging Blocked.';
pushButton.disabled = true;
showSubscriptionObject(null);
return;
}
if ( isSubscribed ) {
pushButton.textContent = 'Disable Push Messaging';
} else {
pushButton.textContent = 'Enable Push Messaging';
}
pushButton.disabled = false;
}
function showSubscriptionObject(subscription) {
const subscriptionJson = document.querySelector('.js-subscription-json');
if ( subscription ) {
subscriptionJson.textContent = JSON.stringify(subscription, null, 4);
}
}
function subscribeUser() {
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(applicationServerPublicKey)
}).then(function(subscription) {
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.');
showSubscriptionObject(subscription);
isSubscribed = true;
updateBtn();
}).catch(function(error) {
console.log('Failed to subscribe the user: ', err);
updateBtn();
});
}).catch(function(err) {
console.log('Failed to subscribe the user: ', err);
});
}
function unsubscribeUser(){
swRegistration.pushManager.getSubscription()
.then(function(subscription) {
if ( subscription ) {
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) {
showSubscriptionObject(null);
console.log('User is unsubscribed.');
isSubscribed = false;
updateBtn();
subscription.unsubscribe();
}).catch(function(error) {
console.log('Error unsubscribing', error.message);
});
}
});
}
function initialiseUI() {
pushButton.addEventListener('click', function() {
pushButton.disabled = true;
if ( isSubscribed ) {
unsubscribeUser();
} else {
subscribeUser();
}
});
swRegistration.pushManager.getSubscription()
.then(function(subscription) {
isSubscribed = !(subscription === null);
showSubscriptionObject(subscription);
if ( isSubscribed ) {
console.log('User IS subscribed.');
} else {
console.log('User is NOT subscribed.');
}
updateBtn();
});
}
if ( 'serviceWorker' in navigator && 'PushManager' in window ) {
navigator.serviceWorker.register('./sw.js').then(function(swReg) {
swRegistration = swReg;
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>
It is not necessary to create a "Progressive Web App (PWA)" to test Web Push as well as no need to create a storage "offline-first"... all the API's
involved in this example (Notifications, Service Worker, Push) work on localhost
without the need for a certificate.
By default browsers identify that the code is running locally and do not apply any kind of imposition normally applied in a "regular" domain (in production).
This is a basic example... the "date" property if added to "payload" really has great importance since it allows the developer to pass additional parameters to notification.
The "codelab" of Google Developers remains the best bad starting point, the "Cookbook" by the same author (Matt Gaunt) is the "Holy Grail" of the implementation.
Sources and references (gist)
footnotes:
¹Push: In the scope of the question is defined the Android system however, for a better understanding and clarification: references to other systems were used.
²SPPCD: Tizen references in his documentation the use of a "push service (sppcd)":
However, the closest I came to a search by the term "sppcd" went to the service "Samsung Push Service".
Pnet recommend you to do the tour to understand how the community works, first try to do something, then ask the question by exposing your question and or problem.
– user81560
Hello pnet, there is no way to explain to you from start to finish how to transfer the domain in a pwa, but there is all the documentation available on google, First you must register a service worker in the domain then in addition to being able to add ico in the android homescreen you can implement the Notifications push service. Follow this documentation is very extensive but once you understand it is very easy to assemble : https://developers.google.com/web/fundamentals/codelabs/push-notifications/? hl=en . Hugs any doubt just post.
– Rafael Salomão
@Rafaelsalomão, I haven’t read all the documentation yet, but I know there’s nothing simple about what I want. So, turn your comment into a response that I mark to not get an open post. I know that the solution is something like you said, including on Xamarin’s website someone answered me very close to what you told me.
– pnet
@Rafaelsalomão, as I said I haven’t read all the documentation yet, I’m doing this, but answer me this. This link passed I have how to make a messaging server? What I want is to have a way to send PN to all App’s developed by the company. And not just on my machine, because I only have two months to develop all this. Server will stay in the company and I leave for other projects.
– pnet
I can open a short answer with a copy of sw.js and a manifesto from one of my domains, just copy and install it on your client’s domain, changing just a few things. This is the first step to having push Notifications turn the domain into a Progressive web app. The domain in question is based on https ?
– Rafael Salomão
I can see yes. If you can send thank you, @Rafaelsalomão
– pnet
Check if customer uses https? I will formulate a response !
– Rafael Salomão
Yes, it does. All because they don’t want to use cloud. It would be much easier, rs. It’s my first time using a local PN server.
– pnet
I’m adding below and saving just so I don’t miss what I wrote.
– Rafael Salomão
@pnet you put "Android only" in your question... What would this be? A native application (app) ? A hybrid application? The native Android browser? Browser applications(s) available on Android?
– Lauro Moraes
@Lauromoraes, is only for Android. Use Forms but is for android only. PN will be only on Android devices.
– pnet