manuel-ch
manuel-ch

Reputation: 11

Progressive web app (PWA) not able to make push notifications pop up on screen of android phone

Working on a PWA (Progressive web app) that needs to create push notifications which are needed to be seen as obvious as possible.

Like most chat apps, i'd like the notification to pop up on the mobile screen. On Android 9 and 10 (not tested other versions), the push notifications makes sound and is added to the drawer, but does not pop up. After some research, i found out that native android apps need to add "notification channels" (feature added with android oreo) to be able to set the app's notification setting to "sound and pop-up" (pop up may also be called heads-up) instead of only "sound". For PWA, i can not find any information about such options.

In the android notification settings of my (installed) PWA, there is one category called "General", Setting is sound only, pop-up / heads-up is not activated. When i activate it, the notifications pops up as expected.

My code uses FCM with VAPID (so no Firebase account is needed), inspired by https://golb.hplar.ch/2019/08/webpush-java.html, https://github.com/ralscha/blog2019/tree/e39cc479044867f40e4c1e2835f7265ee2532760/webpush and https://github.com/web-push-libs/webpush-java

public class PushMessage {
    private final String title;
    private final String body;
    private final String priority = "high";

    public PushMessage(String title, String body) {
        this.title = title;
        this.body = body;
    }

    public String getTitle() {
        return this.title;
    }

    public String getBody() {
        return this.body;
    }

    public String getPriority() {
        return this.priority;
    }

    @Override
    public String toString() {
        return "PushMessage [title=" + this.title + ", body=" + this.body + "]";
    }
}
private boolean sendPushMessage(PushMessage message, Subscription subscription) throws Exception {
        boolean sent = false;
        // send message to given subscription
        byte[] encryptedMessage = new CryptoService().encrypt(
                new ObjectMapper().writeValueAsString(message),
                subscription.getKeys().getP256dh(), subscription.getKeys().getAuth(), 0);

        ECPrivateKey eCprivateKey = (ECPrivateKey) new CryptoService().convertPKCS8ToECPrivateKey(privateBytes);
        String origin = null;
        try {
            URL url = new URL(subscription.getEndpoint());
            origin = url.getProtocol() + "://" + url.getHost();
        } catch (MalformedURLException e) {
            e.printStackTrace();
            //Application.logger.error("create origin", e);
            return true;
        }

        long expiry = new Date().getTime() + 12 * 60 * 60 * 1000;

        JwtClaimsBuilder claimsBuilder = Jwt.claims().audience(origin).subject("mailto:[email protected]");
        JwtSignatureBuilder signBuilder = claimsBuilder.jws();//.algorithm(SignatureAlgorithm.ES256);
        String token = signBuilder.sign(eCprivateKey);

        URI endpointURI = URI.create(subscription.getEndpoint());

        Builder httpRequestBuilder = HttpRequest.newBuilder();
        if (encryptedMessage != null) {
            httpRequestBuilder.POST(BodyPublishers.ofByteArray(encryptedMessage))
                    .header("Content-Type", "application/octet-stream")
                    .header("Content-Encoding", "aes128gcm");
        }
        else {
            httpRequestBuilder.POST(BodyPublishers.ofString(""));
        }

        HttpRequest request = httpRequestBuilder.uri(endpointURI).header("TTL", "86400")
                .header("Urgency", "high")
                .header("Authorization","vapid t=" + token + ", k=" + publicBase64)
                .build();
        try {
            HttpResponse<Void> response = HttpClient.newHttpClient().send(request,
                    BodyHandlers.discarding());

            switch (response.statusCode()) {
                case 201:
                    log.info("Push message successfully sent: "+ subscription.getEndpoint());
                    sent = true;
                    break;
                case 404:
                case 410:
                    log.warn("Subscription not found or gone: "+ subscription.getEndpoint());
                    removeSubscription(subscription);
                    sent = false;
                    break;
                case 429:
                    log.error("Too many requests: "+ request);
                    sent = false;
                    break;
                case 400:
                    log.error("Invalid request: "+ request);
                    sent = false;
                    break;
                case 413:
                    log.error("Payload size too large: "+ request);
                    sent = false;
                    break;
                default:
                    log.error("Unhandled status code: "+ response.statusCode() + " "+ request);
                    sent = false;
            }
        }
        catch (IOException | InterruptedException e) {
            log.error("send push message", e);
        }

        return sent;
    }
client side service worker:

self.addEventListener('push', event => {
  console.log('Push Notification received: ', event);
  if(event.data) {
    let data = JSON.parse(event.data.text())
    event.waitUntil(
      self.registration.showNotification(
        data.title,
        {
          body: data.body,
          icon: 'icons/icon-128x128.png',
          badge: 'icons/icon-128x128.png',
          //image: data.imageUrl,
          image: 'icons/icon-128x128.png',
          vibrate: [100, 50, 100],
          priority: 'max',
          android:{
            priority: 'max'
          },
          data: {
            dateOfArrival: Date.now(),
            primaryKey: 1,
            openUrl: 'https://bar.com',//data.openUrl
            priority: 'max'
          },
          actions: [
            {action: 'explore', title: 'Nice!',
              //icon: 'images/checkmark.png'
            },
            {action: 'close', title: 'Close notification',
              //icon: 'images/xmark.png'
            },
          ],
          requireInteraction : true,
        }
      )
    )
  }
});

As you can see, i tried setting Urgency header and added priority to the message data (server side and client side), all with no effect.

Upvotes: 1

Views: 2180

Answers (1)

Martijn
Martijn

Reputation: 5611

I think you answered your question yourself already. Android apps need to be granted a permission to show a popup, and unless you grant this permission, you won't see a popup. This is a limitation of the Android ecosystem, not of the Web Push protocol or this library.

Upvotes: 1

Related Questions