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.
Related
I have set some mail quota's on email addresses that I manage through Plesk.
However, users are complaining that they are not being informed when their inbox is full or nearly full.
So my idea was to send an email to them when their inbox is about 90% full, so I was wondering if I can retrieve mail account information using PHP?
If not, is there another way of doing this? (I'm not too familiar with console commands)
In this answer I skip the Plesk API altogether, and I assume you either store the properties of the mailboxes in a database, hardcode it, or actually use the Plesk API to retrieve it.
Here is how I retrieve the space used by a mailbox:
function getSpaceUsedByMailBox($username,$password)
{
// open mailbox
$mailBox = imap_open('{localhost:110/pop3/novalidate-cert}INBOX',$username,$password);
// test if successful
$errors = imap_errors();
if ($errors === FALSE)
{
// get info
$info = imap_mailboxmsginfo($mailBox);
// give feedback
echo "Mailbox of $username contains ".$info->Nmsgs.
' messages and is '.$info->Size.' bytes big.';
// flush notices
imap_errors();
imap_alerts();
// close mailbox
imap_close($mailBox);
// return info
return $info;
}
// change this to proper error handling
echo 'ERROR: '.print_r($errors);
// return nothing
return NULL;
}
This is just to give you an idea. You have to adapt it to your coding style.
I am facing issue with Facebook Webhook for feed while messages are working perfectly. For one post i keep on getting multiple notification from Facebook. I have already raised a bug with Facebook and their team is saying that my server is failing to send back 200 OK HTTP status. Also in their doc i have found that
"Your webhook callback should always return a 200 OK HTTP response when invoked by Facebook. Failing to do so may cause your webhook to be unsubscribed by the Messenger Platform."
My code goes like this:
<?php
$challenge = $_REQUEST['hub_challenge'];
$verify_token = $_REQUEST['hub_verify_token'];
if ($verify_token === 'password')
{
echo $challenge;
}
/*........RECEIVING INPUT FROM fACEBOOK.........*/
$input = json_decode(file_get_contents('php://input') , true);
error_log(print_r($input, true));
/*after this i am calling AI and then replying back*/
Is there any way to send back 200 OK status before calling AI in php.
For the Workaround, I have stored the notification in DB and for every notification i am checking existing data (timestamp, senderId, post) in the DB to eliminate the duplicate post.
If anyone is having better option in terms of complexity please let us know.
I a similar issue and in my case I was subscribed to my test app and actual production app. Thus 2 events being sent
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
$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);
}
Environment/Background:
Using PHP Stomp library to send and receive message from ActiveMQ (v5.4.3).
Steps:
Client sends a message with reply-to & correlation-id headers to request queue (say /queue/request)
Subscribe to the response queue (say /queue/response)
Read frame
ack
unsubscribe
The above steps works fine when there is no pending message or pending message < n. In my case, n =200. When the number of pending message is > 200, the message is not delivered. The process waits till timeout and finally timeout without response. I can see the message (using admin UI) after timeout. Here is the code that I'm using for this case:
<?php
// make a connection
$con = new Stomp("tcp://localhost:61616");
// Set read timeout.
$con->setReadTimeout(10);
// Prepare request variables.
$correlation_id = rand();
$request_queue = '/queue/com.domain.service.request';
$response_queue = '/queue/com.domain.service.response';
$selector = "JMSCorrelationID='$correlation_id'";
$headers = array('correlation-id' => $correlation_id, 'reply-to' => $response_queue);
$message = '<RequestBody></RequestBody>';
// send a message to the queue.
$con->send($request_queue, $message, $headers);
// subscribe to the queue
$con->subscribe($response_queue, array('selector' => $selector, 'ack' => 'auto'));
// receive a message from the queue
$msg = $con->readFrame();
// do what you want with the message
if ( $msg != null) {
echo "Received message with body\n";
var_dump($msg);
// mark the message as received in the queue
$con->ack($msg);
} else {
echo "Failed to receive a message\n";
}
unset($con);
Other findings:
Sending messages from one file (say from sender.php) and receive using another script (say receiver.php) working fine.
Allows to send more than 1000 message in the same request queue(and eventually processed and placed in response queue). So it doesn't look like memory issue.
Funny enough, while waiting for timeout, if I browse the queue on admin UI, I get the response.
By default, the stomp broker that I use set the prefetch size to 1.
Without knowing more my guess is that you have multiple consumers and one is hogging the messages in it's prefetch buffer, the default size is 1000 I think. To debug situations like this it's usually a good idea to look at the web console or connect with jconsole and inspect the Queue MBean to see the stats for in-flight messages, number of consumers etc.
Answering my own question.
The problem I'm facing is exactly what is described in http://trenaman.blogspot.co.uk/2009/01/message-selectors-and-activemq.html and the solution is to increase the maxPageSize as specified in ActiveMQ and maxPageSize
As you can match that the 200 is not a varying number, but the default value of maxPageSize.
More references:
http://activemq.2283324.n4.nabble.com/Consumer-is-not-able-to-pick-messages-from-queue-td2531722.html
https://issues.apache.org/jira/browse/AMQ-2217
https://issues.apache.org/jira/browse/AMQ-2745