APNS

Overview explain how it works. Your server acts as provider which stores tokens. Firebase call tokens as IID Token (Instance ID Token).

The token itself is opaque and persistent, changing only when a device’s data and settings are erased Each app instance receives its unique token when it registers with APNs. Device tokens change when the user updates the operating system and when a device’s data and settings are erased. As a result, apps should always request the current device token at launch time.

If APNs attempts to deliver a notification and the destination device is offline, APNs stores the notification for a limited period of time and delivers it when the device becomes available again. This component stores only the most recent notification per device and per app. If a device is offline, sending a new notification causes the previous notification to be discarded. If a device remains offline for a long time, all stored notifications are discarded.

Maximum size is 4KB. Payload is something like: { "aps" : { "alert" : "My message", "badge": 5, "sound": "default" }, "my_custom_data": "my custom data" }

Silent notifications are not meant as a way to keep your app awake in the background, nor are they meant for high priority updates. APNs treats silent notifications as low priority and may throttle their delivery altogether if the total number becomes excessive. The actual limits are dynamic and can change based on conditions, but try not to send more than a few notifications per hour.

Silent notification are configured with {"aps" : { "content-available" : 1 } } and not alert, badge or sound keys.

You can add customer actions using category in notification, so user responds before booting whole app.

Feedback service should be used once a day to get tokens that are not longer active docs

How to make invalid apn token

Feedback service will show tokens that are failed after you last checked. This will not show some invalid tokens that you have generated like: 123123. You need to delete your app manually. Also the token would not be failed if you just delete your app, you need to send message to actually try to use token. After that you will find it in feedback service. When you read feedback service than you need to try again to send to that token to find in in feedback service.

It could be that you use mobile app with different cert than on server. Those tokens will not be invalidated, just messages will not be delivered.

Running MAC in virtualbox is ok when you buy macOS, There are some youtube video video with links to download vmdk file and just use with your virtual box but that is not fair. Than you will get message like:

You cannot sign in to iCloud because there was a problem verifying the identity of this Mac. Try restarting your Mac and signing in again

How to generate Push Notification cert

How to generate Push Notification cert

  • developer.apple.com - account
  • Certificates, Identifiers, … App Ids
  • Push notifications - DEV for testing, production for app store and test flight -> generate
  • Keychain -> Certificate assistant -> generate request for certificate with authority
  • Upload that file to push notification screen on generate Dev/Prod SSL client form
  • Follow steps to generate cert file
  • Import that file as a login keychain (Note that it has to have keychain linked to certificate to be able to export it as a .p12 file)
  • Export certificate as a .p12 file
  • Terminal, navigate to root folder of .p12

    openssl pkcs12 -in yourcert.p12 -out yourcert.pem -nodes -clcerts
    

iPhone tips

  • to close any app use double click on home button and swipe up
  • sometimes you need to restart Settings app (geard icon) to be able to set up variables for specific app

FCM Firebase cloud messaging

Note that GCM (developers.google.com/cloud-messaging) is deprecated so use FCM instead.

For server side in ruby you need to register a project on https://console.developers.google.com and enable “Firebase Cloud Messaging”. Create API key and save as GOOGLE_API_KEY.

More info for server https://firebase.google.com/docs/cloud-messaging/server and error codes https://firebase.google.com/docs/reference/fcm/rest/v1/FcmError

To send test messages to the mobile apps https://console.firebase.google.com/u/0/project/test-gcm/notification/compose

FCM gem

Send message to maximum 1000 tokens (source https://github.com/spacialdb/fcm#usage)

Firebase web

Create project on firebase https://console.firebase.google.com and on Settings -> Tab Cloud Messaging -> Web Configuration -> Web Push certificates -> create key pair. Copy public key as GOOGLE_VAPID_KEY

Examples can be found on https://github.com/firebase/quickstart-js Messaging example you need to update YOUR_PUBLIC_VAPID_KEY_HERE

npm install -g firebase-tools
firebase login
firebase use --add
firebase serve -p 8001

You will also need project number, top righ “Project Settings” and go to the settings It is not Project ID, but it is just 12 numbers like 123123. Save it as GOOGLE_PROJECT_NUMBER

You can install Chrome extension to register a client google chrome example gcm notifications

pushwoosh extension can register but it can not receive messages.

There is nice explanation how to create notification on your site https://developers.google.com/web/fundamentals/push-notifications/

Docs for response

if the value of failure and canonical_ids is 0, it’s not necessary to parse the remainder of the response

If there are failures than iterate one by one and if message is some of the: “Unavailable’, ‘NotRegistered’, ‘InvalidRegistration’ than you can remove it. If you notice registration_id than you should update it token

How to make invalid gcm token

You need to clear all your app data.

cat >> Gemfile <<HERE_DOC
# for notifications Google Cloud Messaging
gem 'gcm'
HERE_DOC
bundle

sed -i config/secrets.yml -e '/^test:/i \
  # google cloud messaging\
  google_api_key: <%= ENV["GOOGLE_API_KEY"] %>'

cat >> config/initializers/google-api.rb <<HERE_DOC
MY_GCM = GCM.new Rails.application.secrets.google_api_key
HERE_DOC

rails g model NotificationToken token kind:integer notifiable:references{polymorphic} # user:references
# make sure kind has default 0, since we can not write that in generator
last_migration

# app/models/concerns/notifiable.rb
module Notifiable
  extend ActiveSupport::Concern
  included do
    has_many :notification_tokens, as: :notifiable
  end
  class_methods do
    def send_messages(message = "Hi All from myapp", collapse_key = nil, additional_data = nil)
      tokens = all.map do |user|
        user.notification_tokens.map(&:token)
      end.flatten
      send_to_tokens(tokens, message, collapse_key, additional_data)
    end

    def send_to_tokens(tokens, message, collapse_key, additional_data)
      data = { message: message }.merge additional_data
      data = data.merge collapse_key: collapse_key
      response = MY_GCM.send(tokens, data: data)
      ApplicationMailer.internal_notification('push message', tokens: tokens, data: data, response_body: response[:body]).deliver_now if Rails.env.development?
      logger.info response[:body]
      response[:body]
    end
  end

  def send_message(message = "Hi from myapp", collapse_key = nil, additional_data = nil)
    tokens = notification_tokens.map(&:token)
    self.class.send_to_tokens(tokens, message, collapse_key, additional_data)
  end
end

# config/routes.rb
+      resources :notification_tokens

# app/controllers/notification_tokens_controller.rb
+ module Api
+   module V1
+     class NotificationTokensController < ApplicationController
+       prepend_before_action :authenticate_current_user
+       def index
+         @notification_tokens = current_user.notification_tokens
+         render json: @notification_tokens
+       end
+ 
+       def create
+         token = notification_token_params[:token]
+         @notification_token = current_user.notification_tokens
+                                           .where(token: token)
+                                           .first_or_initialize
+         if @notification_token.save
+           render json: @notification_token
+         else
+           render json: @notification_token.errors, status: :bad_request
+         end
+       end
+ 
+       private
+ 
+       def notification_token_params
+         params.require(:notification_token)
+               .permit(:token)
+       end
+     end
+   end
+ end

Client

Old plugin PushPlugin has been replaced with new phonegap-plugin-push. You can use ngcordova wrappers, but I found it is easier to use directly.

GCM notifications arrives with very small delay on real devices, but on emulator it could be 10 seconds. Emulator should be build with Google API support (this is important!).

App has 3 states: not loaded (killed, or back button pressed), background and foreground. In every state notification will be shown in android status bar. Clicking on it will load the app again (if it was killed than it will start). Before, when app is foreground than no notification will appear in android status bar. android.forceShow is true than callback is called only when user click on notification (for me sometimes it does use callback at all, for foreground and backgroun case), if false (default) callback is called immediately.

Anyway, when you receive message you can trigger reload for some of your data based on collapse_key. If collapse key is set than any new message with the same collapse_key will overwrite old one. There is limit of 4 different collapse_key at one time. Even I do not use collapse key, new message will overwrite old one stackoverflow

Phonegap plugin push

Install all required packages:

cordova plugin add phonegap-plugin-push --variable SENDER_ID=$GOOGLE_PROJECT_NUMBER
ionic build
android update sdk --no-ui --all --filter "extra-android-m2repository"

PushPlugin

cordova plugin add https://github.com/phonegap-build/PushPlugin.git
ionic state save

# edit www/index.html to add `script-src * 'unsafe-inline' 'unsafe-eval';` to
# the Content-Security-Policy meta

# add `GCM_SENDER_ID: '123123'` to the www/js/app/constants.js

# add `messageService.register();` to  $rootScope.$on('auth:login-success in
# www/js/app.run.js to register new token on user login (validation-success
# is too much, we need just on login)

cat >> www/js/services/message.service.js << HERE_DOC
angular.module('starter')
.service('messageService', function(notifyService, $http, CONFIG, $cordovaPush, $rootScope, $ionicPlatform) {
  this.register = function() {
    $ionicPlatform.ready(function() {
      var androidConfig = {
        senderID: CONFIG.GCM_SENDER_ID,
      }
      notifyService.log({messageService_register: androidConfig});
      $cordovaPush.register(androidConfig).then(function(result) {
        notifyService.log({cordovaPush_register: 'success', result: result});
        // Success
      }, function(err) {
        notifyService.log({cordovaPush_register: 'error'});
        // Error
      });
    });
  };

  $rootScope.$on('$cordovaPush:notificationReceived', function(event, notification) {
    notifyService.log({cordovaPush_notificationReceived: notification.event, notification: notification});
    switch(notification.event) {
      case 'registered':
        if (notification.regid.length > 0 ) {
          saveToken(notification.regid);
          // alert('registration ID = ' + notification.regid);
        }
        break;

      case 'message':
        notifyService.toast('message = ' + notification.message);
        switch(notification.collapse_key) {
         case 'new_order':
           $rootScope.$broadcast('new_order', notification.payload);
           break;
         case 'do_not_collapse':
           break
      }
        break;

      case 'error':
        alert('GCM error = ' + notification.msg);
        break;

      default:
        alert('An unknown GCM event has occurred');
        break;
    }
  });

  // WARNING: dangerous to unregister (results in loss of tokenID)
 //  $cordovaPush.unregister(options).then(function(result) {
 //    // Success!
 //  }, function(err) {
 //    // Error
 //  })

  function saveToken(token) {
    var data = { notification_token: {token: token}};
    $http.post(CONFIG.API_URL + '/notification_tokens', data).then( function(response) {
      notifyService.log({messageService_saveToken: 'sucess', response_data: response.data});
    },
    function (error) {
      notifyService.log({messageService_saveToken: 'error', error: error});
    });
  };
});
HERE_DOC

# add where you want to update data, something like
# +  $scope.$on('new_order', function(event, args) {
# +    notifyService.log({orderController_new_order: args});
# +    getOrders();

This is usefull notification service since $log.debug object does not work.

// www/js/services/notify.service.js
angular.module('starter')
.service('notifyService', function($cordovaToast, $log) {
  this.toast = function(message) {
    var text = message;
    if (typeof message === 'object')
    {
      text = JSON.stringify(message);
    }
    if (window.plugins && window.plugins.toast)
    {
      $cordovaToast.show(text, 'long', 'center');
    }
    else
    {
      alert(text);
    }
    $log.debug(text);
  };

  this.alert = function(message) {
    // alert stays always as opposite to toast which hides automatically
    var text = message;
    if (typeof message === 'object')
    {
      text = JSON.stringify(message);
    }
    alert(text);
    $log.debug(text);
  };

  this.log = function(message) {
    var text = message;
    if (typeof message === 'object')
    {
      text = JSON.stringify(message);
    }
    $log.debug(text);
  }
});

Android Emulator

I found that genymotion works fine. Just install google play services It works for me on Android 5.1. After drag and drop those two files, you need to logs in and open Google+ which will trigger update of Google play services. You can use same login on all emulators. If genymotion emulator dissapears, run adb kill-server to clean connections.

For first message you need to wait minute or two. But than it works instantly.

Service workers

Another cool is service workers https://developers.google.com/web/fundamentals

Firebase Cloud messaging FCM

Upgrade https://developers.google.com/cloud-messaging/faq