Working with Apple Push Notification


What is Apple Push Notification Service

The Apple Push Notification Service is a service created by Apple Inc., it uses push technology through a constantly open IP connection to forward notifications from the servers of third party applications to the Apple devices; such notifications may include badges, sounds or custom text alerts.

As a provider, you need to communicate with the Apple Push Notification Service (APNS) to send the messages that are then pushed to the phone. This is necessary so that the device only needs to maintain 1 connection to the APNS, helping to reduce battery usage.

Basic Structure
  1. You connect to the APNS using your unique SSL certificate
  2. Cycle through the messages you want to send 
  3. Construct the payload for each message
  4. Disconnect from APNS

Device Token

Each push message must be “addressed” to a specific device. This is achieved by using a unique deviceToken generated by APNS within your iPhone application. Once this token has been retrieved, you need to store it on your server.


Implementation

Here is a sample implementation of Apple Push Notification Service through PHP

Create apns.php

<?php

// authentication
$host = "localhost";
$user = "db_username";
$pass = "db_password";
$dbname = "db_name";

// create connection with database
$con = mysql_connect($host,$user,$pass);

// check whether database connection is successful 
if (!$con) {
// if connection not successful then stop the script and show the error
die('Could not connect to database: ' . mysql_error());
} else {
// if database connection successful then select the database
mysql_select_db($dbname, $con);
}

// get the id, token from database
$result = mysql_query("SELECT id,token FROM `device_tokens` ORDER BY id");

//Setup notification message
$body = array();
$body['aps'] = array('alert' => 'This is push message');
$body['aps']['notifurl'] = 'http://www.mydomain.com';
$body['aps']['badge'] = 2;

//Setup stream (connect to Apple Push Server)
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'passphrase', 'password_for_apns.pem_file');
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns_pem_certificate.pem');
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
    stream_set_blocking ($fp, 0); 
// This allows fread() to return right away when there are no errors. But it can also miss errors during 
//  last  seconds of sending, as there is a delay before error is returned. Workaround is to pause briefly 
// AFTER sending last notification, and then do one more fread() to see if anything else is there.

if (!$fp) {
//ERROR
 echo "Failed to connect (stream_socket_client): $err $errstrn";

} else {

// Keep push alive (waiting for delivery) for 90 days
$apple_expiry = time() + (90 * 24 * 60 * 60); 

// Loop thru tokens from database
while($row = mysql_fetch_array($result)) {
$apple_identifier = $row["id"];
$deviceToken = $row["token"];
$payload = json_encode($body);
            
// Enhanced Notification
$msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n", strlen($payload)) . $payload; 
            
// SEND PUSH
fwrite($fp, $msg);

// We can check if an error has been returned while we are sending, but we also need to 
// check once more after we are done sending in case there was a delay with error response.
checkAppleErrorResponse($fp); 
}

// Workaround to check if there were any errors during the last seconds of sending.
// Pause for half a second. 
// Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved
usleep(500000); 

checkAppleErrorResponse($fp);

echo 'Completed';

mysql_close($con);
fclose($fp);

}

// FUNCTION to check if there is an error response from Apple
// Returns TRUE if there was and FALSE if there was not
function checkAppleErrorResponse($fp) {

//byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID). 
// Should return nothing if OK.

//NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait 
// forever when there is no response to be sent.

$apple_error_response = fread($fp, 6);

if ($apple_error_response) {

// unpack the error response (first byte 'command" should always be 8)
$error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response); 

if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';

} else if ($error_response['status_code'] == '1') {
$error_response['status_code'] = '1-Processing error';

} else if ($error_response['status_code'] == '2') {
$error_response['status_code'] = '2-Missing device token';

} else if ($error_response['status_code'] == '3') {
$error_response['status_code'] = '3-Missing topic';

} else if ($error_response['status_code'] == '4') {
$error_response['status_code'] = '4-Missing payload';

} else if ($error_response['status_code'] == '5') {
$error_response['status_code'] = '5-Invalid token size';

} else if ($error_response['status_code'] == '6') {
$error_response['status_code'] = '6-Invalid topic size';

} else if ($error_response['status_code'] == '7') {
$error_response['status_code'] = '7-Invalid payload size';

} else if ($error_response['status_code'] == '8') {
$error_response['status_code'] = '8-Invalid token';

} else if ($error_response['status_code'] == '255') {
$error_response['status_code'] = '255-None (unknown)';

} else {
$error_response['status_code'] = $error_response['status_code'].'-Not listed';

}

echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b>&nbsp;&nbsp;&nbsp;Identifier:<b>' . $error_response['identifier'] . '</b>&nbsp;&nbsp;&nbsp;Status:<b>' . $error_response['status_code'] . '</b><br>';

echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>';

return true;
}
       
return false;
}

?>

Setup Cronjob

While you want to send huge amount of push notification, then it would be better to add to crontab.

Open crontab to add a new cronjob
$ crontab -e

Add a cronjob (Assume you want to run the cron every 5 minutes)
*/5 * * * * myuser /usr/bin/php -f /absolute/path/to/apns.php fetch > /usr/local/apns/apns.log 2>&1 &

Here, the > operator used to redirect the standard output generated by the process to a suitable location, while the & operator sends the process to the background and returns the control to the calling process.

You can discard the output by sending it to /dev/null

It is also a good practice to keep track of id of the process you execute in the background. You can retrive it via the $! operator and store it in a variable for future use.

23 comments:

  1. Replies
    1. Use GCM google provides document for http push
      also u can use gcm for ios now

      Delete
  2. sf android_notify(registration id pass, your message) {

    $headers = array();
    $headers[] = 'Content-Type:application/json';
    $headers[] = 'Authorization:key=your key';

    $data = array(
    'registration_ids' => array($smRegistrationId), //array("key"),
    'data' => array("message" => $ssMessage, "collapse_key" => (string) time()),
    );

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, PUSH_NOTIFICATIONS_GCM_SERVER_POST_URL);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($curl, CURLOPT_POST, TRUE);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
    $response_raw = curl_exec($curl);
    $info = curl_getinfo($curl);

    curl_close($curl);
    $response = FALSE;
    if (isset($response_raw)) {
    $response = json_decode($response_raw);
    //print_r($response);
    }
    return true;
    }

    ReplyDelete
  3. iphone_notify(token, message, if required counter) {

    require_once sfConfig::get('sf_web_dir') . '/ApnsPHP/Autoload.php';

    $push = new ApnsPHP_Push(ApnsPHP_Abstract::ENVIRONMENT_SANDBOX, sfConfig::get('sf_web_dir') . '/ApnsPHP/apns-dev.pem'); //spoco certificate
    $push->setRootCertificationAuthority(sfConfig::get('sf_web_dir') . '/ApnsPHP/entrust_root_certification_authority.pem');

    // Connect to the Apple Push Notification Service
    $push->connect();

    // Instantiate a new Message with a single recipient
    $message = new ApnsPHP_Message($token);

    $message->setCustomIdentifier("Message-Badge-3");

    // $message->setText('You have received new offer.');
    $counter = intval($counter);
    $message->setBadge($counter);

    // Play the default sound
    $message->setSound('sound.caf');
    // Set a custom property
    $message->setCustomProperty('acme2', array('bang', 'whiz'));
    // Set another custom property
    $message->setCustomProperty('acme3', array('bing', 'bong'));
    // Set the expiry value to 30 seconds
    $message->setExpiry(30);
    // Add the message to the message queue
    $push->add($message);
    // Send all messages in the message queue
    $push->send();
    $push->disconnect();
    $aErrorQueue = $push->getErrors();

    if (!empty($aErrorQueue)) {
    //return array(false, $aErrorQueue);
    } else {
    //echo "iPhone - Success";
    }
    //return 1;
    session_unset($_SESSION[$token]);
    return true;
    }

    ReplyDelete
  4. I get this ssl error when sending 500+ messages:

    Warning: fwrite() [function.fwrite]: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry in


    Works fine when sending just a few messages though.

    ReplyDelete
    Replies
    1. Did you find solution to this?

      Delete
    2. Add fclose($fp) after fwrite() function , update SSL package in server and restart server . Before that check push parameters in code, it works for me.

      Delete
  5. Your post of Apple push notification is really impressive.The Apple Push Notification Service is a service created by Apple Inc. Mac OS X Server uses APNs to push the server's mail, calendar and contacts services to network users.
    Thanks.
    For more information please visit here push notifications iphone

    ReplyDelete
  6. I have tried this but it is not working. I have got below error:-

    unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Connection timed out)

    could you please tell me why it is showing.

    ReplyDelete
  7. hello dudes make sure your server is https bcse it does not support http server thanks

    ReplyDelete
  8. in that ssl means secure server only support

    ReplyDelete
  9. How will the device-token - of a fresh installation of my app on a device anywhere of the world - will get into the mysql-database? Until now I hadn't realized this point. Perhaps someone could explain (for dummies ;-))

    ReplyDelete
    Replies
    1. Well your supposed to save it on your server first :) You need to make request to your server from your phone with token before you want to send notification

      Delete
  10. Fahimshakir Freelancer Developer from Delhi-India,You can provide me part time work for Home,Part Time Developer/Freelancer For PHP , WordPress, Magento, Opencart, Shopify, Codeigniter, Website Maintenance in Delhi -india, www.fahimshakir.com

    ReplyDelete
  11. Nice blog...Very useful information is providing by ur blog. Great beginning php tutorials Very clear and helpful for beginners.

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete
  13. guys m beginner to this field so plz tell how it realy works

    ReplyDelete
  14. need code for message notification n email alterz .....

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. Nice One , I used it on http://iphonesoft.fr !
    thank you very much

    ReplyDelete

  17. Nice Blog , This is what I exactly Looking for , Keep sharing more blog .


    Apple iPhone Service Center Chennai, Tamil Nadu

    ReplyDelete