iOS application push notifications - php

We have an application built in PHP that sends out push notifications to both Android and iOS. Our problem is there are some device ids for iOS that seem to completely halt the sending of the all other iOS push notifications in our script, they even say that they have been sent without errors but it stops all notifications in the loop afterwards.
If we then remove the offending device id from our database the sending script works for all devices after the offending device. It is very strange and cant seem to figure out why.
Does anyone have any experience with this? Is it to do with sending to a device ID that doesnt exist anymore will stop apple from completing our script on that specific connection?
Here is our sending script in PHP (this works for all devices apart from the odd rogue device id):
$tHost = 'gateway.push.apple.com';
$tPort = 2195;
$tCert = "path_to_our_cert.pem";
$tPassphrase = 'passphrase';
$tAlert = $this->title; //assign a title
$tBadge = 8;
$tSound = 'default';
$tPayload = $this->body_plain; //assign a body
// Create the message content that is to be sent to the device.
$tBody['aps'] = array ('alert' => $tAlert,'badge' => $tBadge,'sound' => $tSound,);
$tBody ['payload'] = $tPayload;
$tBody = json_encode ($tBody);
$tContext = stream_context_create ();
stream_context_set_option ($tContext, 'ssl', 'local_cert', $tCert);
stream_context_set_option ($tContext, 'ssl', 'passphrase', $tPassphrase);
$tSocket = stream_socket_client ('ssl://'.$tHost.':'.$tPort, $error, $errstr, 30, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $tContext);
if (!$tSocket) exit ("APNS Connection Failed: $error $errstr" . PHP_EOL);
//Loop through all devices to send
foreach($this->device->devices as $item){
if($item->os != "iOS") continue;
if(session::getAdministratorStaffSchoolID() != $item->school_id) continue;
$tToken = $item->device_id;//assign the device id
$tMsg = chr (0) . chr (0) . chr (32) . pack ('H*', $tToken) . pack ('n', strlen ($tBody)) . $tBody;
$tResult = fwrite ($tSocket, $tMsg, strlen ($tMsg));
}
fclose ($tSocket);
Does anyone have any ideas regarding this?
Many thanks

So, just a thought, but you're using the legacy format to send the notification:
$tMsg = chr (0) . chr (0) . chr (32) . pack ('H*', $tToken) . pack ('n', strlen ($tBody)) . $tBody;
And:
Error response. With the legacy format, if you send a notification
packet that is malformed in some way—for example, the payload exceeds
the stipulated limit—APNs responds by severing the connection. It
gives no indication why it rejected the notification. The enhanced
format lets a provider tag a notification with an arbitrary
identifier. If there is an error, APNs returns a packet that
associates an error code with the identifier. This response enables
the provider to locate and correct the malformed notification.
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Appendixes/LegacyFormat.html
So maybe APNS is just dropping the connection on you? That's why all the remaining notifications actually don't get through. Take a closer look at those payloads. And might be a good time to move to the new format.

AFAIK APNs will close the connection as soon as any error occurs (invalid device token, malformed payload, etc.). Your code that sends out Push needs to be prepared to recover from this situation, reconnect and then restart the work.
Take a look at the 3rdParty libraries out there to execute this job for you. You'll rely on the community work.
I can referral Pushy which is written in Java. I have used it in 2 different projects and I can say it works fine and it's an very active project (a lot of discussions and updates there).
I don't know any of them using PHP, but probably there is one

Related

Task progress from a ZeroMQ worker

Fairly new to ZeroMQ. I have a simple REQ/REP queue like below. I am using PHP but that doesn't matter as any language binding would be fine for me.
This is client to request a task
$ctx = new ZMQContext();
$req = new ZMQSocket($ctx, ZMQ::SOCKET_REQ);
$req->connect('tcp://localhost:5454');
$req->send("Export Data as Zip");
echo $i . ":" . $req->recv().PHP_EOL;
And this is a worker to actually perform the task.
$ctx = new ZMQContext();
$srvr = new ZMQSocket($ctx, ZMQ::SOCKET_REP);
$srvr->bind("tcp://*:5454");
echo "Server is started at port $port" . PHP_EOL;
while(true)
{
$msg = $srvr->recv();
echo "Message = " . $msg . PHP_EOL;
// Do the work here, takes 10 min, knows the count of lines added and remaining
$srvr->send($msg . " is exported as zip file" . date('H:i:s'));
}
As the task of exporting data takes about 10 min, I want to connect to the server from a different client and get the progress/ percentage of the task done.
I am wondering if that's even a valid approach.
I tried this approach where REQ/REP part works but I get nothing in PUB/SUB part
Server part
$ctx = new ZMQContext();
$srvr = new ZMQSocket($ctx, ZMQ::SOCKET_REP);
$srvr->bind("tcp://*:5454");
// add PUB socket to publish progress
$c = new ZMQContext();
$p = new ZMQSocket($c, ZMQ::SOCKET_PUB);
$p->bind("tcp://*:5460");
echo "Server is started at port 5454" . PHP_EOL;
$prog = 0;
while(true)
{
$p->send($prog++ . '%'); // this part doesn't get to the progress client
$msg = $srvr->recv();
echo "Message = " . $msg . PHP_EOL;
sleep(2);// some long task
$srvr->send($msg . " Done zipping " . date('H:i:s'));
}
Progress client
$ctx = new ZMQContext();
$stat = new ZMQSocket($ctx, ZMQ::SOCKET_SUB);
$stat->connect('tcp://localhost:5460');
while (true){
echo $stat->recv() . PHP_EOL; //nothing shows here
}
Request client
$ctx = new ZMQContext();
$req = new ZMQSocket($ctx, ZMQ::SOCKET_REQ);
$req->connect('tcp://localhost:5454');
for($i=0;$i<100;$i++){
$req->send("$i : Zip the file please");
echo $i . ":" . $req->recv().PHP_EOL; //works and get the output
}
The concept is feasible, some tuning needed:
All PUB counterparties have to setup any non-default subscription, via, at least an empty subscription .setsockopt( ZMQ_SUBSCRIBE, "" ) meaning receive all TOPICs ( none "filter"-ed out ).
Next, both PUB-side and SUB sides ought get .setsockopt( ZMQ_CONFLATE, 1 ) configured, as there is of no value to populate and feed all intermediate values into the en-queue/de-queue pipeline, once the only value is in the "last", most recent message.
Always, the non-blocking mode of the ZeroMQ calls ought be preferred ( .recv( ..., flags = ZMQ_NOBLOCK ) et al ) or the Poller.poll() pre-tests ought be used to sniff first for a (non)-presence of a message, before spending more efforts on reading its context "from" ZeroMQ context-manager. Simply put, there are not many cases, where blocking-mode service calls may serve well in a production-grade system.
Also some further tweaking may help the PUB side, in case a more massive "attack" comes from the un-restricted pool of SUB-side entities and PUB has to create / manage / maintain resources for each of these ( unrestricted ) counterparties.
You need only use PUB/SUB if there is more than one client wanting to receive the same progress updates. Just use PUSH/PULL for a simple, point to point transfer that works over tcp.
Philosophical Discussion
With problems such as this to solve there's two approaches.
Use additional sockets to convey additional message types,
Use just two sockets, but convey more than one message type through them
You're talking about doing 1). It might be worth contemplating 2), though I must emphasise that I know next to nothing of PHP and so don't know if there are language features that encourage one to have separate request and progress clients.
If you do, your original client needs a loop (after it has sent the request) to receive multiple messages, either progress update messages or the final result. Your server, whilst it is doing its 10 minute lookup, will send regular progress update messages, and the final result message at the end. You would probably use PUSH/PULL client to server, and the same again for the progress / result from the server back to the client.
It is architecturally more flexible to follow 2). Once you have a means of sending two or more message types through a single socket and of decoding them at the receiving end, you can send more. For example, you could decide to add a 'cancel' message from the client to the server, or a partial results message from the server back to the client. This is much easier to extend than to keep adding more sockets to your architecture simply because you want to add another message flow between the client and server. Again, I don't know enough about PHP to say that this would definitely be the right way of doing it in that language. It certainly makes a lot of sense in C, C++.
I find things like Google Protocol Buffers (I prefer ASN.1) very useful for this kind of thing. These allow you to define the types of messages you want to send, and (at least with GPB), combine them together inside a single 'oneof' (in ASN.1 one uses tagging to tell different messages apart). GPB and ASN.1 are handy because then you can use different languages, OSes and platforms in your system without really having to worry about what it is being sent. And being binary (not text) they're more efficient across network connections.

APN Working with One Device but Not with Multiple Devices

I am trying to send push notification to a bunch of iPads and tested it first with the device I have. It worked just fine. Then I tried to expand it to include multiple devices, including my own, and it doesn't seem to work (checked with other relevant device holders just to make sure). The device token information appears to be just fine. What could be wrong? Maybe it's a throttling issue?
I tried to rewrite everything from the following tutorial and the issue is still the same:
http://learn-php-by-example.blogspot.co.il/2013/01/working-with-apple-push-notification.html
I get no error in the checkAppleErrorResponse function.
Here is the code that changes when changed from one device to many:
foreach ($device_tokens as $token)
{
$apple_identifier = '[censored]';
$payload = json_encode($body);
$msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $token)) . pack("n", strlen($payload)) . $payload;
/* Actually sends the notification */
fwrite($fp, $msg);
$this->checkAppleErrorResponse($fp);
}
Update: I tried to manually copy some of the tokens and do random tests on when devices receive the notifications. Although I can't say the results are conclusive, it turns out that when all tokens are certainly valid (devices that exist in our company), the messages go through. However, when I add at least one device that I can't check personally (means it might be invalid, or the user refused notifications), none of the devices in the list get messages.
The question is, does this mean Apple requires the entire array of tokens to be valid devices whose owners authorized APNs? (Seems completely illogical, but it's the only explanation I can think of).

Only half of push notifications reach devices (APNS)

$usersStmt = self::$db->query("SELECT `token` FROM `tokens`");
$users = $usersStmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($users AS $userToken) {
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $userToken)) . chr(0) . chr(strlen($payload)) . $payload;
$result = fwrite($fp, $apnsMessage);
}
I have a table with 10 push notification tokens inside of it. Using PHP, I fetch these tokens and send a notification for each token to APNS.
The problem is, only half are actually getting the notifications. I have a feeling that this is because one of the tokens is invalid (or something along those lines) and it is stopping it from sending the rest of the tokens.
I have checked the value of $result and each one returns true. This means that all notifications are being successfully sent to APNS but not delivered to all devices.
Is it possible that an invalid token could disrupt the rest of the notifications from reaching devices? I'm not sure what's going on here but I know all the notifications are sent successfully to APNS but not reaching the devices.
Any ideas?
Invalid tokens will not disrupt the rest of the notifications from reaching devices.
I have the same issue before, the problem was with messages sent itself.
Check your messages length, the notification payload is 256 bytes ).
Check your messages encoding, use the escape sequence for none ASCII messages.
If you send to same device multiple times, Apple will send the last notification only.
Make sure your tokens is for the same working environment, sandbox tokens will not work with production server.
Check Apple technical notes for more info:
https://developer.apple.com/library/mac/technotes/tn2265/_index.html#//apple_ref/doc/uid/DTS40010376-CH1-TNTAG23
As stated in the Apple Documentation:
"Remember that delivery of notifications is “best effort” and is not guaranteed."
(Linked relevant question with the same answer - thanks to Joe)
Are APNS now guaranteed delivery given that passbook passes are guaranteed
The current adoption rate of iOS 7 to iOS 8 is 50/50. Have you made sure your apps register separately for each OS in your didFinishLaunching call in the AppDelegate? Does the data suggest it's working for one OS and not the other? example code in App Delegate:
if(iOS8){
[self.application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[self.application registerForRemoteNotifications];
NSLog(#"Register for iOS 8 Notifications %#", application);
}else{
// iOS 7 Notifications
[self.application registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
NSLog(#"Register for iOS 7 Notifications %#", application);
}

Broadcasting Push Notification via PHP

I have a list of push notification tokens (around 10k) for my app, and I am not tracking the states of active/deleted tokens. When a new user is added to the system, it is added with its push token
(one device can hold one push notification token, in case a token is changed -not happened in the last 6 months of my app's life cycle- it is modified with the new token)
For sending push notification to a single user I am using the following php script
<?php
// connecting to sql
sql_connect();
// get user token from sql
$token = sql_gettoken();
// Put your device token here (without spaces):
$deviceToken = $token;
// Put your private key's passphrase here:
$passphrase = 'my_passphrase';
// Put your alert message here:
if ($_GET["message"]) $message = $_GET["message"];
else $message = 'This is a default message in case no message is defined';
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'production_cerfiticate.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
echo 'Message not delivered' . PHP_EOL;
else
echo 'Message successfully delivered' . PHP_EOL;
// Close the connection to the server
fclose($fp);
?>
I believe if I keep $fp (connection to APNS Server) open I can send multiple push notifications in between (I am not sure of that?). But I have read that if a token is offline/deleted app then the connection is closed by apple's side, which means push notification cycle will fail.
Any tips for converting this single push notification script into broadcast notification ? I am discouraged to test this since the first test will be a live broadcast and I have only 1 shot at this.
EDIT: I have missed the most important part, If I just go and call this php script in a loop for all device tokens, which means it will try to call this for deleted apps and actives, will Apple block/deactivate/ban my certificate or my ability to send push notifications ?
For others out there looking for broadcast, it seems Apple does not have any limits for push notifications.
Cheers
You should keep the connection open and send notifications using the same connection as long as it stays open. That's what Apple says you should do.
If the app is uninstalled from a device, the connection won't be closed when sending to that device. It will be closed only if the token was never valid for the current environment (which usually happens when you try to send a sandbox device token to the production environment or vice versa), or if the message has other errors (such as too long payload).
When you send a notification to a device that uninstalled the app, APNS figures out that the device uninstalled the app, and later, when you call the feedback service, you get that device token. Apple asks that you call the feedback service periodically and remove the devices that are returned by it from your DB. Apple might block you if you don't use the feedback service and keep sending notifications to devices that uninstalled the app multiple times.
I suggest you read about Troubleshooting Push Notifications, especially the Push Notification Throughput and Error Checking section. It's very helpful.

Critical outcome when sending push notification from our server

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

Categories