PHP technique to query the APNs Feedback Server

28,391

Solution 1

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
    ),
)

Solution 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?

Share:
28,391
Admin
Author by

Admin

Updated on February 17, 2020

Comments

  • Admin
    Admin over 4 years

    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

  • Admin
    Admin over 14 years
    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.
  • strange
    strange over 14 years
    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]; }
  • Admin
    Admin over 14 years
    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!! Thanks
  • Admin
    Admin over 14 years
    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?
  • brack
    brack about 14 years
    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.
  • Vaerenberg
    Vaerenberg about 13 years
    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!
  • Daniel  Magnusson
    Daniel Magnusson about 12 years
    "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 ;(
  • Julian F. Weinert
    Julian F. Weinert over 11 years
    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.
  • Andy Ibanez
    Andy Ibanez over 10 years
    Instead of checking the timestamp, can't I simply check for all the returned tokens then and remove them from my database?
  • Nick Baicoianu
    Nick Baicoianu over 10 years
    Absolutely. That's what we do in our implementation – the timestamp data isn't valuable to us, so we ignore it.
  • tamak
    tamak over 9 years
    @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.
  • Nick Baicoianu
    Nick Baicoianu almost 9 years
    Yep, we still use this code in our production apps. It's run as an hourly cron job, separate from our main APNS connection.
  • Vaibhav Saran
    Vaibhav Saran about 5 years
    now the code returns all device tokens and no timestamp is there.