Apns Gcm Push Notifications Using Ionic
Contents |
RPush
https://github.com/rpush/rpush
You can test sending notification from firebase https://console.firebase.google.com/u/1/project/movebase/notification or using curl as in this example https://github.com/firebase/quickstart-js/tree/master/messaging#curl
Register with npm package inside webpacker https://firebase.google.com/docs/web/setup#add-sdks-initialize
If rpush does not work (ie queued: 123 and not queued: 0), you can restart every day
0 0 * * * su - ubuntu -c 'cd /home/ubuntu/myapp/current && if ! /home/ubuntu/.rbenv/bin/rbenv exec bundle exec rpush status | grep -q "queued: 0"; then echo `date` restarting because queued is not zero | sudo tee -a /home/ubuntu/myapp/current/log/rpush.log; sudo monit restart rpush;echo restarted; fi' >> /home/ubuntu/myapp/current/log/crontab.log;
Old docs
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
Upgrade https://developers.google.com/cloud-messaging/faq 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. Instead of google console you can create a project in firebase console https://console.firebase.google.com/
Best way to start is video https://www.youtube.com/watch?v=BsCBCudx58g 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
There are two formats of payloads: notification (with eventual data) and data https://firebase.google.com/docs/cloud-messaging/concept-options#top_of_page There are fields that needs to be send https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#Notification
- data message, does not contain notification and can trigger onMessageReceived when app is both in foreground, background, killed
- notification message, set the
notification
key. It can include optionaldata
payload which can be consumed by client app. For general notificaiton you can use title and body, and for android, you can add other fields. https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#androidnotificationtoken/topic/condition: "registration_id/weather/foo in topics", notification: { title: 'Message title', body: 'message body', icon: 'myicon', // this can override default lancher icon from manifest color: '#FFFFFF', sound: 'default', // can be my_sound which is located in /res/raw tag: 'post_123', // replace existing notification with same tag in drawer click_action: 'intent'. // If specified, an activity with a matching intent // filter is launched when a user clicks on the notification. body_loc_key: 'my_key', // key used to localize message body_loc_args: [], // agrs used for localization keys title_loc_key: ''. // same as body title_loc_args: [], // same as body channel_id: '', // Android 8 API 26, notifications are separated by channels https://developer.android.com/guide/topics/ui/notifiers/notifications#ManageChannels } android: { collapse_key: 'new_post' // max 4 different keys, only last from the same key will be send on resume priority: 'normal', // normal or high (consumes more battery) restricted_package_name: 'com.trk.web', ttl: '600' //time to live data: { } notification: { } } webpush: { } apns: { }
For Rails we use https://github.com/spacialdb/fcm gem which use Legacy http
server
protocol
Send message to maximum 1000 tokens source
First argument to fcm.send
is what is actual registration_ids
(if it is a
single string than it is converted to array) and it is merged to second options
param. It can contain
collapse_key
is used to avoid sending too many same messages when the device comes back online, max is 4 different keyspriority
normal or highcontent_available
andmutable_content
iOS specifics-
time_to_live
seconds how long it should be on server if device is offline, default is 4 weeks data
custom key value that can be used by the app-
notification
object that sets visible parts of the message. not all keys are the same for android and iOS, you can see two tables on https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-supportnotification: { title: 'hello' // title is not visible on iOS phones and tables body: 'Body text' sound: 'default` // file name from /res/raw (android) or Library/Sounds (ios) click_action: // corresponds to category (ios) or activity intent (android) // when app is not in foreground, it will open the app body_loc_key: // for localisation purpose body_loc_args: title_loc_key: title_loc_args: (ios only) badge: 'new', // if 0 than badge is removed (android only) android_channel_id: 'channel_id' // icon: 'file_name' // file name is from drawable resource tag: 'post_123', // it replaces notification with a same tag in drawer but // only for when app is not in foreground color: '#ffffff' // icon color }
To receive data and notification in android app you can follow https://www.youtube.com/watch?v=we8eGwIED8Y
# config/initializers/fcm.rb
MY_FCM = FCM.new Rails.application.secrets.firebase_cloud_messaging_server_key
# app/services/firebase_cloud_messaging.rb
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);
}
});
Service workers
Another cool thing is service workers https://developers.google.com/web/fundamentals