Wednesday, 20 June 2012

Implementing APNS in Titanium-Start to End Tutorial

Hi,
Today we will be implementing the APNS(Apple Push Notification Service) using Titanium.
I will guide you from your client code to the server side implementation.


Get Started

Client Implementation

Create a Titanium Mobile Project.
Once it is done, open your app.js and paste this code




Titanium.Network.registerForPushNotifications({  
   types: [  
     Titanium.Network.NOTIFICATION_TYPE_BADGE,  
     Titanium.Network.NOTIFICATION_TYPE_ALERT  
   ],  
   success:function(e)  
   {  
     var deviceToken = e.deviceToken;  
     Ti.API.info("Push notification device token is: "+deviceToken);  
     alert('device token is' +e.deviceToken);  
     Ti.API.info("Push notification types: "+Titanium.Network.remoteNotificationTypes);  
     Ti.API.info("Push notification enabled: "+Titanium.Network.remoteNotificationsEnabled);  
   },  
   error:function(e)  
   {  
     Ti.API.info("Error during registration: "+e.error);  
   },  
   callback:function(e)  
   {  
     // called when a push notification is received.  
    //Titanium.Media.vibrate();  
    var data = JSON.parse(e.data);  
    var badge = data.badge;  
    if(badge > 0){  
     Titanium.UI.iPhone.appBadge = badge;  
    }  
    var message = data.message;  
    if(message != ''){  
     var my_alert = Ti.UI.createAlertDialog({title:'', message:message});  
     my_alert.show();  
    }  
   }  
  });   
 }  


In the success function you can write your own code or call a webService where you will be sending your device token to the server.

So this was the easy part. Once this is done, we need to generate push certificate.


Generating Certificates


The first thing you need is your Push certificates. These identify you when communicating with APNS over SSL.
Generating the Apple Push Notification SSL certificate on Mac:
  1. Log in to the iPhone Developer Connection Portal and click App IDs
  2. Ensure you have created an App ID without a wildcard. Wildcard IDs cannot use the push notification service. For example, our iPhone application ID looks something like AB123346CD.com.serverdensity.iphone
  3. Click Configure next to your App ID and then click the button to generate a Push Notification certificate. A wizard will appear guiding you through the steps to generate a signing authority and then upload it to the portal, then download the newly generated certificate. This step is also covered in the Apple documentation.
  4. Import your aps_developer_identity.cer into your Keychain by double clicking the.cer file.
  5. Launch Keychain Assistant from your local Mac and from the login keychain, filter by the Certificates category. You will see an expandable option called “Apple Development Push Services”
  6. Expand this option then right click on “Apple Development Push Services” > Export “Apple Development Push Services ID123″. Save this as apns-dev-cert.p12 file somewhere you can access it.
  7. Do the same again for the “Private Key” that was revealed when you expanded “Apple Development Push Services” ensuring you save it as apns-dev-key.p12 file.
  8. These files now need to be converted to the PEM format by executing this command from the terminal:
openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12  
 openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12  

9. If you wish to remove the passphrase, either do not set one when exporting/converting or execute:
openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem
10. Finally, you need to combine the key and cert files into a apns-dev.pem file we will use when connecting to APNS:
cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem
It is a good idea to keep the files and give them descriptive names should you need to use them at a later date. The same process above applies when generating the production certificate.
At this step we are done with our certificate generation. if you are interested in detail info, then I recommend this Link

Server Implementation

This is the .php script that you have to run in order to see push on your device.

<?php  
     $serverId = "0b9214ea";  
     $name = "APNS";  
     $apnsPort = 5223;  
     $passPhrase = "alpha";  
     $fwrite = "";  
     $sslUrl = "ssl://gateway.sandbox.push.apple.com:" . $apnsPort;  
     $apnsCert = "";//give the apns.pem file path on your server  
     $badge = 1;  
     $message = 'My first Push notification';  
     $apnspayload['aps'] = array ('alert' => $message,'badge' => $badge,'sound' => 'default');  
     $payload = json_encode($apnspayload);  
      $tokenId = 'c6xxxxxx b1xxxxbf 1bxxxxxa bxxxxx41 cxxxxxc9 04xxxxxd xxxxxbd0 0xxxxxxx'; //masked due to security reason, you should add your device token ID here  
     $apnsMessage = chr(1) . pack('N', time()) . pack('N', time() + 86400) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $tokenId)) . chr(0) . chr(strlen($payload)) . $payload;  
   $streamContext = stream_context_create();  
   stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);  
   stream_context_set_option($streamContext, 'ssl', 'passphrase', $passPhrase);  
   $apns = stream_socket_client($sslUrl, $error, $errorString, 6, STREAM_CLIENT_CONNECT, $streamContext);  
     if($apns){  
   $fwrite = fwrite($apns, $apnsMessage);  
     fclose($apns);  
   @socket_close($apns);  
     }else{  
     echo 'request failed';  
     }  
 ?>  

Couple of things I want to make it clear in the above .php code is

  • You might have to change the apns port, but in most cases this will work.
  •  Make sure you give the correct path for your apns.pem file that you created in the above Generate certificate section
  •  You Should put your device token ID, so make sure you have your device token ID.

Final Touch

After following the above steps, go to your Apple account and create a new provisioning profile, with the App ID you created, add your device to that provisioning profile.

All the steps to create a provisioning profile are here Create Provision Profile.
After creating the Provisioning profile download that profile and install it by double clicking it.
Open up your Xcode organizer and drag and drop the provision profile you downloaded to the device in which you want to test the APNS, for that your device should be connected to your machine and your device should allow development.

Open up your Titanium studio and run the Application on the device, It will ask you for a valid provisioning profile, just locate it to the path where you downloaded the profile from you apple account.
And you are done.

If you have any issues with the implementation, then happy to comment.

Happy Push Notification.

34 comments:

  1. Thanks a lot for your tutorial.

    I am new to mobile development and I seem to have the certificates up and running. The PHP script does not show any error which supposedly means it is fine. However, in my App I do not receive any notifications.. also, both iTunes and Titanium show a different Device ID when I check so.

    Could you perhaps tell me what I might be doing wrong?

    ReplyDelete
  2. If you have all the valid certificates and the php scripts is running fine then make sure the provision profile in your device is having push notification enabled.

    Different Device ID?? are you referring to this code 'Ti.API.info("Push notification device token is: "+deviceToken); ' This is device token

    ReplyDelete
  3. Thanks for your response, the problem was with the provisioning profiles.

    Now, I have one question though.. I get the message of push notifications and didnt have the correct device token set in the php script. I hoped that if I delete the app I would have to re-enable push notifications so I can mail the device token.

    How can I reset this so I can sent my device token via xhr to my php script?

    Thanks again!

    ReplyDelete
  4. Its simple,
    In your app.js ->registerForPushNotifications function -> inside success function,

    After you get the device token just create a xhr request right after that and call your API and send the device token.

    ReplyDelete
  5. Thanks again, the problem now though is that I do not get a message asking me if I want to allow push notifications it looks like the whole RegisterForPushNotifications is not being recognised.

    While it worked at first and I accepted it and noted (possibly) the wrong deviceToken as no pushes are received on the iPhone. That is why I think I copied the deviceToken wrong.. Now I want to redo that process of allowing push notifications

    ReplyDelete
  6. Hi Vincent,
    The reason for you not getting the message for notification is that..
    According to Apple-
    The first time a push-enabled app registers for push notifications, iOS asks the user if they wish to receive notifications for that app. Once the user has responded to this alert it is not presented again unless the device is restored or the app has been uninstalled for at least a day.

    Have a look at this below given link
    http://developer.apple.com/library/ios/#technotes/tn2265/_index.html%23//apple_ref/doc/uid/DTS40010376-CH1-TNTAG42

    ReplyDelete
  7. Thanks a lot!

    I am starting to hate Apple, I just want to develop, stupid rules and certificates.

    I really appreciate your help!

    Cool new design by the way :)

    ReplyDelete
  8. Glad it helped..
    Well I am a iOS developer, learning Titanium is fun too, but if you really wanna build something out of the box then Apple is God in it.
    Follow the rules and play safe.. :)
    Thanks for the design appreciation

    ReplyDelete
  9. Back again.. no luck on other devices either.

    Push notifications are excepted but when the script is run nothing is received.
    THe script does seem to recognize the .pem file which supposedly is accepted (with a password). Upon dumping the $apns "resource(3) of type (stream)" is the result which, to me suggests it is working correctly (?)

    Sorry for bothering you but what more can go wrong?

    ReplyDelete
  10. with other device again have you checked the valid provision profile added to device....
    I checked with my two devices and the above implementation works.
    Check whether you are successfully sending the new device token to server.

    ReplyDelete
  11. Hi yea, the provisioning profiles are valid on al devices of our team. So that is why I asked what else could go wrong other then wrong provisioning files or a certificate which don't work.

    ReplyDelete
  12. just to cross check copy paste the new device token ID to the script and then try. If you get the notification then surely the new device token is not getting to server.
    Thats only i think will be the problem..

    ReplyDelete
    Replies
    1. Done.. nothing received.. We have checked all our certificates and also tried different ports on the server.
      2195 seems to be sending and receiving something but nothing is received on the phone. All other ports give warnings:

      Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:5223 (Connection refused)

      I used 2195 because I read it on http://blog.serverdensity.com/how-to-build-an-apple-push-notification-provider-server-tutorial/. Does this mean the ports are not correctly set?

      Delete
  13. Hi Vincent,

    Just make sure the network on which you are connected is not blocking port 5223.

    For APNs traffic to get past your firewall, you'll need to open these ports:

    TCP port 5223 (used by devices to communicate to the APNs servers)
    TCP port 2195 (used to send notifications to the APNs)
    TCP port 2196 (used by the APNs feedback service)
    TCP Port 443 (used as a fallback on Wi-fi only, when devices are unable to communicate to APNs on port 5223)

    ReplyDelete
    Replies
    1. Hi Ajeet,

      I got it working, really don't know how.. perhaps it were the certifications or not. All ports are blocked here on wifi except 2195. I want to thank you for all the help, I really appreciate it!

      Kind regards,
      Vincent

      Delete
    2. Glad that I was able to help you. Stay tune to this blog, I will be posting some cool stuffs you can do on iOS..

      Thanks

      Delete
  14. Sorry to ask for your help once more..

    I am now successfully receiving the notifications, but it seems as if the callback function is not called. What I did to test this is to set the application badge to 202(hardcoded in the callback) and the message to dump of variables (JSON.stringify(data)). But the application badge always resembled the APNS Payload. So it seems as though the badge and message are not set using the callback, is this correct? Because I disabled the my_alert var now and I still receive a message.

    In the callback I want to add some extra functionality, but whenever I put a request to a php script or just a simple static alert these do not show.

    Another question I have, whenever a user is using the app and a notification is send the notification is not received, it only is when the app runs on the background.

    Have I done something wrong or do I misunderstand the way notifications work?

    Thanks alot!

    ReplyDelete
  15. Hi Vincent,
    The badge and message will be set via your php script, in the call bask you are just getting the info from the message and showing it. And I think when you are on the application or your application is in foreground you wont see the push notification in effect.
    Check below link, the first line will clear your doubt http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction/Introduction.html%23//apple_ref/doc/uid/TP40008194-CH1-SW1

    ReplyDelete
    Replies
    1. Hi Ajeet, the above link is not working. will you guide me how to resolve my issue. my issue is I am receiving a notification when the app runs on the background.

      I want to show the notification when user working in any screen also. I am new to development please do help me this.

      Delete
  16. I seriously miss a "like" button on your articles, thanks again!

    ReplyDelete
    Replies
    1. Thanks a lot Vincent, That surely made my efforts worth.. :)

      Delete
  17. Hi.
    Very cool your tuto.

    Just a question. I made an application sur Iphone. This application get a RSS and I would like the badge is incremented when there is a new article. But just badge, no alert and vibrate, juste badge.

    Thanks

    ReplyDelete
    Replies
    1. Hi Stephane,

      To just make Badge work, when registering for push notification make sure the type is like
      types: [
      Titanium.Network.NOTIFICATION_TYPE_BADGE,

      ],

      and later in your code handle only for badge not the alert

      Delete
  18. Hi,
    I agree about the tute!

    I have a quick quesiton, I am not getting notificaitons on my device, and I have checked the following:
    - provision files on device are fine (removed and re-installed a couple of times (:D)
    - php script is running with error number 0 and no error string

    Does the device ID you see have dashes ('-') in it, or was that you masking the id ... mine is just a continuous string of numbers and letters.

    Could it be that I have typed it in incorrectly - I see you have suggested waiting a day to reinstall to get the id again - I couldn't cut and paste since it was on device :)

    Thanks in advance for your help .....

    ReplyDelete
    Replies
    1. Also, I am using port 2195, and I have tried setting $passPhrase to "alpha" as in your example, nothing and also the password I was forced to use creating the cert pem file. I still don't get the notification on device, but the brach in the code after "if($apns){" seems to be running with fwrite showing that bytes have been written.

      Delete
    2. Funnily enough, I just tried using the device id after storing it like so : Ti.App.Properties.setString('devicetoken',e.deviceToken); When you get it back, it is missing the first 2 characters - this device ID works for me!

      var data = JSON.parse(e.data); is now failing for me though :D

      Delete
    3. OK, so this is me answering my own queries :D

      1) Your app.js code "alert('device token is' +e.deviceToken);" should be "alert('device token is: ' +e.deviceToken);" it lead to me making a school boy error adding the "is" onto the device ID

      2) var data = JSON.parse(e.data); doesn't work for me (Ti SDK 3.0.0GA) since "e" is already an object and not a JSON parseable string

      3) It would also interest people to know that you can pass custom parameters as well - that way your app can respond accordningly (see http://stackoverflow.com/questions/8134155/apple-push-notification-with-sending-custom-data). Take the note that there is 256 bytes max available here so be careful.

      Kind regards,
      Brad

      Delete
  19. Heya.. :) I'm glad have this post.. But when I execute php i get 'request failed'. Dunno how to figure it out ;(

    ReplyDelete
  20. After implementing this tutorial, using Titanium 3.2.0.GA I was not receiving the push notifications on my test device. This is the .js code that ended up working for me so that we successfully received the push notifications on the device:


    // Push Notifications - Register
    Ti.Network.registerForPushNotifications({
    types : [Ti.Network.NOTIFICATION_TYPE_BADGE, Ti.Network.NOTIFICATION_TYPE_ALERT, Ti.Network.NOTIFICATION_TYPE_SOUND],
    success : deviceTokenSuccess,
    error : deviceTokenError,
    callback : receivePush
    });

    // Process Incoming Push Notifications
    function receivePush(e) {
    //alert('receivePush: ' + JSON.stringify(e, null, 4));
    //alert(e.data.aps.alert);
    alert(e.data.alert.body);
    }

    // Save the Device Token for subsequent API calls
    function deviceTokenSuccess(e) {
    deviceToken = e.deviceToken;
    //alert('deviceTokenSuccess: ' + deviceToken);
    }

    function deviceTokenError(e) {
    alert(e.error);
    }

    ReplyDelete
  21. Hi All,
    Thanks for sharing your valuable comments. It helped me a lot.
    Now I faced one problem in this.
    Push notification works fine and shows the exact info as sent by the server. But the badge is not getting updated. It shows only last received badge. whenever the app is in background or not opened.

    When app is in foreground and app received a notification then "callback" method gets called. where we can do manipulation for updating badge. but this function is not getting called in background.

    What to do to update the badge number??

    --
    Thanks
    Chetan Jadhav

    ReplyDelete
  22. Hi,

    Thanks for great post.

    I have one problem. Here is my scenario

    Scenario 1
    I got the notification message. I click the notification. App is open and "callback" function is working. So, i got notification message. this scenario is ok.

    Scenario 2
    I got the notification message. I don't click the notification. I open the app directly. In this case "callback" function is not working.
    How to know the notification message, when directly open the application?

    Thanks,
    Thet Paing

    ReplyDelete
  23. Hi.,
    I have implementing this push notification.

    If i have implementing with the ACS accelerator server., am getting the push notification.But if i have implementing with the php script., am not yet receive a notification. am getting a "Request Failed" error . Please give me a solution.

    I have using a following code :

    $message,'badge' => $badge,'sound' => 'default');
    $payload = json_encode($apnspayload);
    $tokenId = 'c6xxxxxx b1xxxxbf 1bxxxxxa bxxxxx41 cxxxxxc9 04xxxxxd xxxxxbd0 0xxxxxxx'; //masked due to security reason, you should add your device token ID here
    $apnsMessage = chr(1) . pack('N', time()) . pack('N', time() + 86400) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $tokenId)) . chr(0) . chr(strlen($payload)) . $payload;
    $streamContext = stream_context_create();
    stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
    stream_context_set_option($streamContext, 'ssl', 'passphrase', $passPhrase);
    $apns = stream_socket_client($sslUrl, $error, $errorString, 6, STREAM_CLIENT_CONNECT, $streamContext);
    if($apns){
    $fwrite = fwrite($apns, $apnsMessage);
    fclose($apns);
    @socket_close($apns);
    }else{
    echo 'request failed';
    }
    ?>

    ReplyDelete
  24. How we get device token in appcelerator

    ReplyDelete
  25. Hi Ajeet,

    I get a "Connection refuses" error when I try to connect to the server.

    Can you help me sort out this error?

    Thanks in advance.

    ReplyDelete

Pages