Create my own Push mail server

Asked

Viewed 3,822 times

1

To use PN (Push Notification) I usually make use of cloud servers such as Firebase, Azure, etc.

Now I’ve picked up a service to do and my client demands that we have our own server.

I saw this link on Soen, but if someone has something simpler and want to share, thank you. Only Android.

  • 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.

  • 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.

  • @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.

  • @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.

  • 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 ?

  • I can see yes. If you can send thank you, @Rafaelsalomão

  • Check if customer uses https? I will formulate a response !

  • 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.

  • I’m adding below and saving just so I don’t miss what I wrote.

  • @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?

  • @Lauromoraes, is only for Android. Use Forms but is for android only. PN will be only on Android devices.

Show 6 more comments

2 answers

7


Implementing Progressive Web APP on your domain.

Requisítos Mínimo:

  1. The domain must be over https (ssl encryption).
  2. The domain must have a valid manifest file.
  3. The domain must pass a browser audit that I will describe below.

Well, to have the web push Notifications It is necessary to first transform your domain into a Progressive web app. In addition to the push Notifications various other advantages are provided, such as static cache direct from the browser which allows you to browse over the domain with the offline device, the addition of a shortcut icon directly inserted into the user’s homescreen and navigation in the form of a mobile app without a browser dependency.

Registering a manifest file:

At the root of your domain you should include a json file let’s call it the.json manifest in this example:

{
  "short_name"       : "teste",
  "name"             : "meudominio.com",
  "display"          : "standalone",
  "background_color" : "#131b26",
  "theme_color"      : "#131b26",
  "icons"            : [{  "src"  : "tema/BlueRED/images/48x48.png",
                           "type" : "image/png",
                           "sizes": "48x48"
                        },
                        {  "src"  : "tema/BlueRED/images/96x96.png",
                           "type" : "image/png",
                            "sizes": "96x96"
                        },
                        {  "src"  : "tema/BlueRED/images/192x192.png",
                           "type" : "image/png",
                           "sizes": "192x192"
                        },
                        {  "src"  : "tema/BlueRED/images/256x256.png",
                           "type" : "image/png",
                            "sizes": "256x256"
                        },
                        {  "src"  : "tema/BlueRED/images/512x512.png",
                           "type" : "image/png",
                           "sizes": "512x512"
                        }],
  "start_url": "/ utm_source=homescreen&utm_medium=pwa&utm_campaign=pwa"
} 

You will also need a large format image (512x512), to generate smaller images as in the sizes described in our manifest.json above. These images are used by android to add icon to the user’s homescreen depending on the size of the display. There are tools that scale the image for you by simply having an image in the format above measured to not lose resolution. Also the web application creates the manifest for you alone, just fill out the form. Use this tool to create your.json manifest and scaled images. (Generator Manifesto Json)

After generating your manifest file you should indicate in the head of the domain that it exists by adding the following goal on all pages.

<link rel="manifest" href="/manifesto.json" />

Creating a file with the routines of your service worker.

Definição by Google :

A service worker is a script that your browser runs second to separate from the web page, enabling features that are not need a web page or user interaction. Currently, Workers service already includes features such as push notifications and background sync.

Registering a sw.js. file (Our Service Worker)

This my solution is aimed at static caching of all domain features, which allows a navigation even if offline.

var CACHE_VERSION = 26;  
/* Esse é o número de versão do cache muito importante. Se mudar a versão 
força todos os navegadores que possuem nosso service worker registrado, a 
excluírem suas copias e solicitarem ao servidor uma nova copia dos arquivos 
cacheados. */
var CURRENT_CACHES = {
    prefetch: 'prefetch-cache-v' + CACHE_VERSION
};

self.addEventListener('install', function(event){
   /* Dentro dessa lista é adicionado todos os arquivos estáticos do domínio 
   um por um, antes que o usuário solicite a pagina todo esse conteúdo é 
   baixado para o navegador utilizando uma cabeça em background do 
   navegador. Você pode cachear páginas,imagens,css entre outras 
   coisas  que nem foram abertas ainda. 
   O resultado é um site absurdamente rápido, e  com o tempo de 
   permanência exageradamente alto. Alguns de meus domínio possuem 12 
   minutos ou mais de duração média da visita, assim como a taxa de rejeição 
   despencou*/ 
   var urlsToPrefetch = [ '/tema/BlueRED/images/pix.gif',
                           '/js/sprite.js',
                           '/tema/BlueRED/images/640X400.jpg' ];
    console.log('Handling install event. Resources to prefetch:', urlsToPrefetch);

    self.skipWaiting();

    event.waitUntil(
        caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
            return cache.addAll(urlsToPrefetch);
        })
    );
});

self.addEventListener('activate', function(event){
/*Se este nome de cache não estiver presente na matriz de nomes de cache 
"esperados", então exclua-o.*/
    var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
        return CURRENT_CACHES[key];
    });

    event.waitUntil(
        caches.keys().then(function(cacheNames){
            return Promise.all(
                cacheNames.map(function(cacheName) {
                    if (expectedCacheNames.indexOf(cacheName) === -1) {
                        console.log('Deleting out of date cache:', cacheName);
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});

self.addEventListener('fetch', function(event){
    console.log(event.request);
    if(!event.request.headers.get('range')){
        event.respondWith(
            /* caches.match  irá procurar um entrada no cache de browser 
            para a requisição feita para o arquivo estático interceptando 
            qualquer requisição até seu servidor, caso encontre ele responde 
            ao cliente localmente do navegador.*/               
            caches.match(event.request).then(function(response){
                if (response) {
                    /* Se encontrou uma resposta válida para o arquivo 
                    solicitado responde localmente com o cache */
                    console.log('Found response in cache:', response);
                    return response;
                }
                /* Caso não encontre o arquivo cacheado efetua uma 
                requisição até o servidor para solicitar o arquivo */
                console.log('No response found in cache. About to fetch from network...');
                // event.request will always have the proper mode set ('cors, 'no-cors', etc.) so we don't
                // have to hardcode 'no-cors' like we do when fetch()ing in the install handler.
                return fetch(event.request).then(function(response){
                    console.log('Response from network is:', response);
                    caches.open(CURRENT_CACHES.prefetch).then(function(cache){
                      if(response.type != 'opaque' && response.type != 'cors' && response.url != 'https://www.meudominio.com/sw.js'){
                        /* Se a requisição tiver sido efetuada essa rotina 
                       adiciona o arquivo ao cache, mas antes faz o devido 
                       tratamento para requisições opacas e o propio arquivo 
                       sw.js não ser cacheado.*/
                       console.log("cache adicionado:"+response.url);
                       cache.add(response.url);
                      }
                    });
                    return response;
                }).catch(function(error){
                    console.error('Fetching failed:', error);
                    throw error;
                });
            })
        );
    }
});       

Finally, there must be a javascript routine contained on all pages of your domain, so that at the first user access to the domain make the registration of our service worker, if it has not been registered in this browser yet.

In this case we can implement in the form below:

if('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
      navigator.serviceWorker.register('/sw.js').then(function(registration) 
     {
       // Registration was successful
       console.log('ServiceWorker registration successful with scope: ', 
       registration.scope);
     }).catch(function(err) {
          // registration failed :(
         console.log('ServiceWorker registration failed: ', err);
     });
    });
}

function waitUntilInstalled(registration){
   return new Promise(function(resolve, reject){
       if (registration.installing) {
           // If the current registration represents the "installing" service worker, then wait
           // until the installation step (during which the resources are pre-fetched) completes
           // to display the file list.
          registration.installing.addEventListener('statechange', function(e) {
           if (e.target.state === 'installed'){
              resolve();
           }else if (e.target.state === 'redundant'){
              reject();
           }
       });
     }else{
          /* Otherwise, if this isn't the "installing" service worker, then 
          installation must have been */
          /* completed during a previous visit to this page, and the resources are already pre-fetched.*/
          // So we can show the list of files right away.
          resolve();
     }
  });
}

if('serviceWorker' in navigator){
    navigator.serviceWorker.register('/sw.js',{
        scope: '/'
    }).then(waitUntilInstalled)
      //.then(showFilesList)
     .catch(function(error) {
       // Something went wrong during registration. The service-worker.js file
       // might be unavailable or contain a syntax error.
       document.querySelector('#status').textContent = error;
     });
 }else{
      // The current browser doesn't support service workers.
      var aElement         = document.createElement('a');
      aElement.href        = 'http://www.chromium.org/blink/serviceworker/service-worker-faq';
      aElement.textContent = 'Service workers are not supported in the current browser.';
      document.querySelector('#status').appendChild(aElement);
 }    

This routine is executed before the opening of the domain, and is responsible for validating and registering the service worker for the domain. After all this done, should open the console in google Chrome and perform the validation of all the above steps, using the tool in the tab audits browser. If there is any problem with the implementation the browser debug itself will provide you the documentation page with the explanation of the errors.

inserir a descrição da imagem aqui

After PWA is implemented, you can start the implementation of web push Notifications, this documentation will be useful to you Push Notifications. I hope the humble post helps!

  • Any additional questions add skype salom4598. Hugs!

  • Dude, I’m trying to implement this. I’m going to try first on my website, so I’m transferring to Cloudflare, to switch from HTTP to HTTPS. After receiving the certificate, I will test your code, but looking at the file manifesto.json what those images inside json are for?

  • I changed the answer

  • I have a question in the URL. Example before the bar would be my default url, right? And I see that you put several after the bar. What does that mean

4

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:

push


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

inserir a descrição da imagem aqui

  • 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)": Tizen sppcd However, the closest I came to a search by the term "sppcd" went to the service "Samsung Push Service".

  • After a meeting between the parties involved in the project, I talked about the subject and resolved the following: There is no more need for PN, but that the App receives visually(badge) information. This badge is a contactodor. I believe this way it can come from a service (REST), this was decided last night. If that’s the case, I’ll open another thread to that question, which I think is outside the scope of the current question.

  • Lauro, your post is very well written and explained. congratulations!! Independent reply or no, thank you!!! Thanks!!!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.