1

We have an app on appstore, and with registered push notifications. They have successfully worked all the time, but we now tried to send a 'global' push, and something weird happened. This is what we have in our server-side .php file:

//Loop through tokens in tokenArray
$i = 0;
$t = 0;
foreach($tokenArray as $token)
{
    $t++;
    // Make notification
    $msg = chr(0) . pack('n', 32) . pack('H*', $token) . pack('n', strlen($payload)) . $payload;

    // Send
    $result;
    if($message != null)
    {
        $result = fwrite($fp, $msg, strlen($msg));
    }

if ($result)
    $i++;
}
// Close the connection to the server
fclose($fp);

if($i == 0)
{
    echo 'The message was not delivered to anyone out of '.$t.'.';
}
else
{
    echo 'The message was delivered to '.$i.' out of '.$t.'.';
}

The code before this has always worked, and it kind of still does. The tokenArray contains the table with tokens, as in SELECT Token FROM Tokens; from our SQL. This works.

During development, when only our own tokens were registered, it always said "The message was delivered to 4 out of 4", even though we had deleted our apps from our phones. Now we tried to send to all ≈1100 registered tokens with this code. The message was sent, and the output was "The message was delivered to 588 out of 1194." And we did not receive the notification ourselves! What does that mean?

After about 5 minutes, I switched out the tokenArray with an array only containing my own tokens and sent a new push, and I received that one on my phone. I also know for a fact that the 'working' token exist in the previous 'tokenArray' which failed(I checked).

Is push notification a game of chance!? What does it mean when if($result) fails? And why did it fail over 500 times?

The certificates and .pem and .p12 etc are all working, the only thing I did different from push1 to push2 was to use another table which is a clone from the original table in my SQL-server. Table2 only have my tokens, and it worked. No other changes was made. Only SELECT Token FROM Tokens2, and later I proved that all the tokens in Tokens2 exist in Tokens I have no idea if anyone got the push at all, or if the 'lucky' 588 of the 1200 that still has the app installed received it.

What causes this? We don't dare send another one in case half of them already received it.. Is there some limit to how fast I can send pushes at once? Or what are we doing wrong?! Please help, thanks.

Eran
  • 387,369
  • 54
  • 702
  • 768
Sti
  • 8,275
  • 9
  • 62
  • 124
  • "What does that mean?" > Means `fwrite` wrote zero bytes or returned `false`. What was the mode used with `fopen`? Also, `if($message != null)` is $message defined or is that supposed to be $msg? What does `$result;` on its own line do? – ficuscr Feb 05 '13 at 20:11
  • So how does each message differ? Any commonalities between the ones that failed (your code might want to better track that in the loop)? Are there things like usernames in the payload? Are there any multi-byte characters? I see you are using non multi-byte string functions. – ficuscr Feb 05 '13 at 20:16
  • @ficuscr This service is sort of a "news" feed, that alert users of a spesific kind of news about the app's content. We only send a string (`$message`). This string is received to this .php from a html-form using `POST`. There is nothing wrong with the variables, and it's just a plain message. The same message is supposed to be sent to all users. And I sent the same message to both tables, as I said in the question. Also, `$fp = stream_socket_client(...);` if that matters. What Eran said in his answer would make sense though.. – Sti Feb 05 '13 at 22:18

2 Answers2

2

Well, I don't know php, so your code doesn't help me. However, based on your description it's likely some of the device tokens in your DB are invalid. When Apple's server gets a notification with an invalid device token it closes the socket. If you already wrote more messages after the one with the bad token, they won't reach Apple. Only after you detect that the socket was closed and open a new one your messages will reach Apple. If you don't use the enhanced notification format, it would be a good idea to start using it - this way you can get from Apple the id of the invalid message and clean your DB from invalid tokens. However, even using the enhanced format doesn't guarantee that you'll detect all the errors (unless you are willing to send the messages really slowly, and check for error responses from Apple after each message you send).

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Does this mean that the 588th token was invalid, and that the rest of them didn't receive? Or that it might as well have been the first token that was invalid? And how is it possible to have an invalid token? The only INSERT into that table is from the app itself, in didRegisterForNotification-blahblah.. And what is the 'Enhanced' format you talk about? – Sti Feb 05 '13 at 22:08
  • If you send a sandbox token to the production APN server, or a production token to the sandbox APN server, the token is considered invalid. Perhaps your DB contains both sandbox & production tokens. – Eran Feb 05 '13 at 22:58
  • As for the first question, I don't understand the php code, so I don't know what this number means. But it doesn't necessarily mean the 588th token was invalid. It is possible to send messages to the APN server without getting an error response. Once you get the error response, you know that all the messages that were sent after the failed message didn't reach Apple. However, if you miss the error response and the connection just gets closed, you have no way of knowing which messages were sent and which were not. – Eran Feb 05 '13 at 23:02
1

Your main loop does not take into account cases in which Apple will close socket connections. As mentioned by Eran, if you send an invalid token, Apple closes the connection at which point, any further writes using fwrite will fail. So if your 589th token is invalid, no other push will be sent to Apple.

Here's a simple fix for that that fits into your logic; this part replaces the if statement in the main loop:

if ($result) {
    $i++;
} else {
    fclose($fp);
    // Add code here to re-open socket-connection with Apple.
}

Besides the enhanced notification format mentioned by Eran, you can also use the APNS Feedback API to query Apple for invalid tokens and purge them from your database. You can find more infomation on that here: http://bit.ly/14RPux4

There is no limit to how many push notifications you can send at once. I've sent thousands in a few seconds. The only real limitation is the connection between you and the APNS servers.

Nick
  • 2,573
  • 19
  • 21
  • Thank you. Do you know how a token can turn invalid? The only `INSERT INTO` to that spesific table is in `DidRegisterForPush...` in the app, they would all be valid when inserted, how do they determine what a valid token is? – Sti Feb 06 '13 at 15:07
  • I'd like to clarify that the device tokens you get from the Feedback service are not invalid tokens. Sending messages with such device tokens to Apple doesn't result in an error response and doesn't close the socket. The tokens returned by the Feedback service are device tokens of devices that uninstalled the Application. – Eran Feb 06 '13 at 15:09
  • @Eran, that's not entirely true. Feedback Service returns tokens that were unable to receive a message. One of the possible reasons can be uninstallation but it can also simply be that the device was not reachable for some time (user went offline). The Feedback Service returns a timestamp associated with the token and can often return the same token with several timestamps marking different attempts. Sti : The token is valid until it is marked invalid by Apple for one reason or another. User might deactivate push for your app, or uninstall your app, or wipe his device, etc. – Nick Feb 07 '13 at 13:48
  • @Nick What I meant is that the Feedback service returns device tokens that used to be valid at some point (or are still valid for other applications on the device, since all applications on a device share the same token). You are correct in saying that uninstalling the app is not the only reason for getting a device token from the feedback service. On the other hand, invalid tokens are tokens that were never valid for the current environment. Sending a notification with an invalid token results in the closing of the socket and the return of an error response which error code 8. – Eran Feb 07 '13 at 15:11
  • @Eran What do you mean by "current enviroment"? I have tried sending push-notification to myself after I've uninstalled the app, and the socket is not closed. The "enviroment" hasn't changed, so what has..? – Sti Feb 08 '13 at 17:48
  • @Sti By environment I mean either sandbox or production. For each application, each environment has its own certificate, its own provisioning profile and its own device tokens. They shouldn't be mixed. Sending a notification with a sandbox token using a production certificate (or vice versa) will result in an error response due to invalid device token. You should make sure that all the device tokens in your DB belong to the same environment, and that you are using the certificate that matches that environment. – Eran Feb 08 '13 at 18:25
  • @Eran Oh I see. Yes, Both my development token and my distribution token are registered in both tables, but I didn't get error(and i received the message) when I tested it on the small table with only my mixed enviroment-tokens. So maybe if it tries to send the message to the development-token when on release BEFORE trying to send it to the distribution one it might fail..!(?) And if that's the case, then my own development token screwed up the actual push for everyone AFTER my token in the push-loop.. – Sti Feb 11 '13 at 18:36
  • @Eran Actually, when I think of it, when I send a distribution push notification to device-tokens in a table containing four tokens of mixed enviroments (2 of each), then it prints out "Successfully sent message to 4 out of 4". So I don't get error for this.. But the development-enviroment-devices obviously don't receive the push though.. – Sti Feb 11 '13 at 18:38
  • @Sti You can't rely on that success message. Apple don't send responses for successful delivery, so after sending the 4 notifications to Apple, you can't be sure if any of them were actually accepted by Apple. It's possible, for example, that the 4 messages arrived together to the Apple server. Then, when processing the 2nd message, Apple detected an invalid token and closed the connection. In that case, the 3rd and 4th messages are ignored. If you don't try to read the error responses from Apple, you'll have no chance to discover that error. Even if you do, you might still miss it. – Eran Feb 11 '13 at 20:17
  • When you sent the 4 messages, it's possible that the first 2 were the ones with the valid tokens, and therefore were sent successfully. Then when Apple processed the 3rd message it found an invalid token and closed the socket, but you didn't notice it, because you didn't try to send more messages to the socket and you didn't try to read error responses from the socket. – Eran Feb 11 '13 at 20:20