PHP technique to query the APNs Feedback Server
Asked Answered
E

5

30

Can someone clarify what the APNs (Apple Push Notification) wants as far as how you query it?

The docs say it starts sending as soon as the connection is made. Does this mean that I don't do an fread() on it?

Here's my current code to try and read it. I did NOT put the fread() in a loop as I do not know what response indicates "no more records to read" and I didn't want an infinite loop on my server.

<?php
$apnsCert = 'HOHRO-prod.pem';

$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
stream_context_set_option($streamContext, 'ssl', 'verify_peer', false);

$apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);

echo 'error=' . $error;
echo 'errorString=' . $errorString;


$result = fread($apns, 38);
echo 'result=' . $result;


fclose($apns);
?>

So far all I am getting is a null reply. There are no errors so it is connecting.

I don't know if the null reply means no data is there, or my fread() is the wrong way to do it.

Thanks

Excerpta answered 14/8, 2009 at 16:27 Comment(4)
Even I have a similar code and I too get null from fread. Not sure whether my code is really working and APNS is sending out a NULL reply or just something is missing. I'm sure my connection to APNS is successful. I had installed my application on some 5-10 devices and send couple of alerts and then uninstalled the application from few and started sending the alerts again so that APNS feedback server would let us know it failed to deliver on few devices. I haven't got anything other than blank in fread() :-( If you have done something different and got it working please do let me know.Simasimah
Have you used "pushutil"? Once you figure it out, it's a faster way to check the feedback server. It's a Mac OS X utility that you compile and then run from the Unix command line. Google Erica Sadun - it's on her site under Push.Excerpta
Still getting no response from the feedback server, even when I send out 76 push notifications - some of them HAD to be unsuccessful. Same thing happens when I use Erica Sadun's "pushutil" command-line utility - the push goes thru fine, then I remove the app and push again, and STILL nothing ever comes up on the feedback server. And this is with Erica's app so I know it must work. I think the problem must be the certificate. I am using the same push-production cert that I use to push. There's not a separate feedback cert, is there?Excerpta
Today I found that fread() won't work because APNs feedback sends garbage data until the actual feedback. This causes fread to fail, because it's reading null/empty data. As gw1921 mentions below, you must loop on feof() until strlen(fread($apns, 38)). From that point on, you'll have valid feedback. Then, unpack the data as Nick B below has suggested. (Another nasty gotcha: the uninstalled app won't generate feedback unless you have another app on the device with the same aps-environment. So you need 2 push-enabled sandbox apps on your device for the uninstalled one to populate feedback.)Natica
D
69

Here's a big gotcha which confused me when I first tried connecting: the APNS feedback servers only return the device tokens that have "expired" since your last feedback request. Which means most of the time you'll get a NULL response unless you're already dealing with a high volume of users of your app.

So make sure you store the expired device tokens to disk or db, because after your feedback query they're gone for good. This makes testing a pain to say the least!

Here's a complete function to fetch the device tokens from the APNS feedback servers (many thanks to the answers above for helping me put it all together):

function send_feedback_request() {
    //connect to the APNS feedback servers
    //make sure you're using the right dev/production server & cert combo!
    $stream_context = stream_context_create();
    stream_context_set_option($stream_context, 'ssl', 'local_cert', '/path/to/my/cert.pem');
    $apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $errcode, $errstr, 60, STREAM_CLIENT_CONNECT, $stream_context);
    if(!$apns) {
        echo "ERROR $errcode: $errstr\n";
        return;
    }


    $feedback_tokens = array();
    //and read the data on the connection:
    while(!feof($apns)) {
        $data = fread($apns, 38);
        if(strlen($data)) {
            $feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
        }
    }
    fclose($apns);
    return $feedback_tokens;
}

If all is well, the return values from this function will look something like this (via print_r()):

Array
(
    Array
    (
        [timestamp] => 1266604759
        [length] => 32
        [devtoken] => abc1234..............etcetc
    ),
    Array
    (
        [timestamp] => 1266604922
        [length] => 32
        [devtoken] => def56789..............etcetc
    ),
)
Dolley answered 19/2, 2010 at 19:8 Comment(10)
Thanks for this elegant solution. Now I just need to know how to compare the timestamp returned by the above feedback code to the timestamp that I have saved in my database, which of course represents the latest time that the device sent me a token. Do I just do a straight integer compare? Or do I have to convert the timestamps to correct endianness or something?Excerpta
This answer should be the accepted solution, IMO. This is basically what I'm doing, and it works perfectly. If you're still not seeing feedback data with this method, verify that you have another app on the device w/push enabled that reports to the same aps-environment as the uninstalled app. Johnny- regarding timestamp, once you unpack it as in Nick's post, you can do an integer compare.Natica
In which case would I not get a response from the feedback server? My notifications go through fine (as I can say from the logs) but for some reason I never get any notifications on the device!Runlet
"the APNS feedback servers only return the device tokens that have "expired" since your last feedback request." ouch, i should really read documentation I guess ;(Higgledypiggledy
I tried using this function. I'm really sure I'm using the right certs / server combo. I'm always using .sandbox. I'm sending two push notifications to dev devices, one device gets the message and the feedback array is empty.Dacca
Instead of checking the timestamp, can't I simply check for all the returned tokens then and remove them from my database?Knitting
Absolutely. That's what we do in our implementation – the timestamp data isn't valuable to us, so we ignore it.Dolley
@NickBaicoianu is the code you posted here still what you currently use for detection of invalid device tokens? I've spent a week finally getting my push notifications working PERFECTLY except for the fact that while sending, the connection to apns is dropped as soon as an old / invalid device token is detected / attempted. I tried to use your code here, substituting my certificate and array of tokens, but I get nothing.Ballot
Yep, we still use this code in our production apps. It's run as an hourly cron job, separate from our main APNS connection.Dolley
now the code returns all device tokens and no timestamp is there.Watertight
O
2

That code looks right however you need to loop and check for end of stream in order to read all the device codes.

 while (!feof($apns)) {
        $devcon = fread($apns, 38);
 }

However my problem is the actual unpacking of the data. Does anyone know how to unpack the binary data which you've just read to get the actual device ID (as string) along with the timestamp etc?

Ordzhonikidze answered 14/10, 2009 at 15:4 Comment(2)
I think this is the right idea - $array = unpack("NnH32", $result); $feedbackTime = $row[0]; $feedbackLen = $row[1]; $feedbackUDID = $row[2]; That unpacks the 38 bytes sent by the feedback server. However, the 32-bit date value is in network order, or big-endian. If someone can supply a PHP function that will flip this 4 bytes to Intel (little-endian) order, I think we have the solution. NOTE: the actual UDID is a character string and does NOT need to have its order flipped.Excerpta
This any good? --------- /* Convert float from HostOrder to Network Order / function FToN( $val ) { $a = unpack("I",pack( "f",$val )); return pack("N",$a[1] ); } / Convert float from Network Order to HostOrder */ function NToF($val ) { $a = unpack("N",$val); $b = unpack("f",pack( "I",$a[1])); return $b[1]; }Ordzhonikidze
U
1

I got the solution from apple forum and it is for development. Try this for production also.

"Well, as dumb as it sounds, I found a solution:

Create a dummy app id in the program portal, enable development push notifications on it Create and download the associated provisioning profile Create a new xcode project, and invoke the registerForRemoteNotificationTypes method on start. Install the dummy app on your device. At this point, you should have two DEVELOPMENT apps running on your device: the original app and the dummy app. Both should be registered to receive push notifications. Uninstall the original app, and try to send a push notification to that app. Invoke the feedback service, and you should receive data back."

Unconnected answered 5/10, 2009 at 11:49 Comment(0)
O
0

This finally worked for me.

$arr = unpack("H*", $devconts); 
$rawhex = trim(implode("", $arr));

$feedbackTime = hexdec(substr($rawhex, 0, 8)); 
$feedbackDate = date('Y-m-d H:i', $feedbackTime); 
$feedbackLen = hexdec(substr($rawhex, 8, 4)); 
$feedbackDeviceToken = substr($rawhex, 12, 64);

And then you simply check for the device token against the timestamp!

Ordzhonikidze answered 15/10, 2009 at 20:50 Comment(1)
This works great, gw1921. I am storing the $feedbackDate in a SQL column for now. What type of data should the column be? I set it to Integer and that is giving me "2009". The other 2 columns, length and token, work GREAT!! ThanksExcerpta
S
0

Just started using this library - works great for me!

https://github.com/mac-cain13/notificato

Syncytium answered 19/2, 2016 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.