Finally,I found out the sandbox tokens,which were mixed with the production tokens,and I update the code,receive the error response from apple,when post a sandbox token to apple,the next tokens all failed....While the push msg was "$payload['aps'] = array('content-available' => 1); ",apple will push the msg ,but the device will not show this msg,when I find the wrong one,I delete it,and retest.
There is an amusing problem of apple push notification service:
when I generate a post notification for my device and send to apple's server,for a while,my device will receive the notification.
But if I first post a notification by a wrong devicetoken,then post a right notification to my device,my device will be responseless,just like the push notification was eaten by apple?
What wrong with this?
I use php code for push service,here is the most important part:
$payload['aps'] = array('content-available' => 1);
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
foreach ($line as $deviceToken) {
break;
}
print_r($deviceToken);
$apnsMessage = chr(1) . $time . $time . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($payload)) . $payload;
$ret = fwrite($apns, $apnsMessage);
if ($ret !== strlen($apnsMessage)) {
echo "push error";
}
ob_flush();
flush();
$read = array($apns);
$null = null;
$changedStreams = stream_select($read, $null, $null, 0, 100000);//wait for 0.1s
if ($changedStreams === false) {
echo "Error: Unabled to wait for a stream availability";
} elseif ($changedStreams > 0) {
print "can't receive any notification behind this token";
$responseBinary = fread($apns, 6);
if ($responseBinary !== false || strlen($responseBinary) == 6) {
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
print_r($response);
}
} else {
//update the success push status in database
}
print "<br>";
}
I dont know what's wrong with this,and I have also add feedback service in another php,and get none wrong token from apple,Because I have many devicetoken in my database,I cant find which one of them is wrong.
Someone help me,forgive my poor english
Assuming your $apns is implemented in this way
$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2,STREAM_CLIENT_CONNECT, $streamContext);
you should check the $errorString to find out the error.
or try to close the socket for each message and see if there is any difference
fwrite($apns, $apnsMessage);
socket_close($apns);
fclose($apns);
Related
I have a little problem with notification push and iOs 10 (with Swift 3)..
I followed this tutorial : https://www.sitepoint.com/developing-push-notifications-for-ios-10/
With Pusher, it's ok, works fine..
I receive the notification on my phone.
But I would like to send notifications from my web server..
I generate my .pem file (http://www.apptuitions.com/generate-pem-file-for-push-notification/)
My php code :
$apnsServer = 'ssl://gateway.sandbox.push.apple.com:2195';
$privateKeyPassword = 'mykey';
$message = "My message here !";
$deviceToken ='MYTOKENHERE';
$pushCertAndKeyPemFile = 'pushcert.pem';
$stream = stream_context_create();
stream_context_set_option($stream,'ssl','passphrase',$privateKeyPassword);
stream_context_set_option($stream,'ssl','local_cert',$pushCertAndKeyPemFile);
$connectionTimeout = 30;
$connectionType = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
$connection = stream_socket_client($apnsServer, $errorNumber, $errorString, $connectionTimeout,$connectionType,$stream);
if (!$connection){
echo "Failed to connect to the APNS server. Error = $errorString <br/>";
exit;
}
else{
echo "Successfully connected to the APNS. Processing...</br>";
}
$messageBody['aps'] = array('alert' => $message,'badge' => 1, 'sound' => 'default');
$payload = json_encode($messageBody);
$notification = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
$wroteSuccessfully = fwrite($connection, $notification, strlen($notification));
if (!$wroteSuccessfully){
echo "Could not send the message<br/>";
} else {
echo "Successfully sent the message<br/>";
}
Result :
Successfully connected to the APNS. Processing...
Successfully sent the message
But I never receive notification on my phone
Why ?
How to send a notification push with php ?
Thanks for your help :)
[EDIT]
Ok, i found a solution..
Just add at the end:
fclose($connection);
I'm trying to implement the APNS (Push notification).
I've created the certificates, etc.. everything was created like iv'e done many many times.
but i cant get an error from apple APNS server and it seems that everything is ok but i still not getting any push in the devices.
this is the PHP code I'm using to make the magic :)
I ensure that the PORTS are open in the hosting.
basically I'm getting the user token from the DB and sending the push text with default sound.
can someone please look and tell me if you can see any problem in this code or let me know how can i get the real result of the process / response from APNS Server?
<?php
error_reporting(0);
header("Content-type: application/json; charset=utf-8");
include "dbconn.php";
$data = array();
$json= array();
$users = array();
$opponent_id = $_REQUEST['opponent_id'];
$sender_id = $_REQUEST['sender_id'];
$message = $_REQUEST['message'];
$sql = "SELECT * FROM `registration` WHERE `chat_id` ='$opponent_id'";
$result = mysql_query($sql);
if (!$result) {
echo "Could not successfully run query ($sql) from DB: " . mysql_error();
exit;
}
if (mysql_num_rows($result) == 0) {
echo "No rows found, nothing to print so am exiting";
exit;
}
while ($row = mysql_fetch_assoc($result)) {
$token = $row['token'];
}
echo "\n$token";
$passphrase = 'certificate_password';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
//stream_context_set_option($ctx, 'ssl', 'local_cert','push/some_prod_certificate.pem');
stream_context_set_option($ctx, 'ssl', 'local_cert','push/some_dev_certificate.pem');
stream_context_set_option($ctx, 'ssl', 'cafile', 'push/entrust_root_certification_authority.pem');
# Open a connection to the APNS server
$fp = stream_socket_client(//'ssl://gateway.push.apple.com:2195', $err,
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp){
echo "Error: ".$err;
exit;
}
echo "\n".'Connected to APNS Push Notification' . PHP_EOL;
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
$payload = json_encode($body);
echo "\n$payload";
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $token) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
//set blocking
stream_set_blocking($fp,0);
//Check response
checkAppleErrorResponse($fp);
// Close the connection to the server
fclose($fp);
function checkAppleErrorResponse($fp) {
$apple_error_response = fread($fp, 6); //byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID). Should return nothing if OK.
//NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait forever when there is no response to be sent.
if ($apple_error_response) {
$error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response); //unpack the error response (first byte 'command" should always be 8)
if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';
} else if ($error_response['status_code'] == '1') {
$error_response['status_code'] = '1-Processing error';
} else if ($error_response['status_code'] == '2') {
$error_response['status_code'] = '2-Missing device token';
} else if ($error_response['status_code'] == '3') {
$error_response['status_code'] = '3-Missing topic';
} else if ($error_response['status_code'] == '4') {
$error_response['status_code'] = '4-Missing payload';
} else if ($error_response['status_code'] == '5') {
$error_response['status_code'] = '5-Invalid token size';
} else if ($error_response['status_code'] == '6') {
$error_response['status_code'] = '6-Invalid topic size';
} else if ($error_response['status_code'] == '7') {
$error_response['status_code'] = '7-Invalid payload size';
} else if ($error_response['status_code'] == '8') {
$error_response['status_code'] = '8-Invalid token';
} else if ($error_response['status_code'] == '255') {
$error_response['status_code'] = '255-None (unknown)';
} else {
$error_response['status_code'] = $error_response['status_code'].'-Not listed';
}
echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b> Identifier:<b>' . $error_response['identifier'] . '</b> Status:<b>' . $error_response['status_code'] . '</b><br>';
echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>';
return true;
}
echo "\nPush respnse OK";
return false;
}
?>
You can't get error responses from Apple because you are using the simple binary format that doesn't return error responses :
$msg = chr(0) . pack('n', 32) . pack('H*', $token) . pack('n', strlen($payload)) . $payload;
If you want a format that can returns an error response, you can use one of the enhanced formats.
For example, the format that starts with 1 :
The msg would start with chr(1), followed by 4 bytes of message ID, 4 bytes of expiration time and the rest of the message would be the same as what you have now pack('n', 32) . pack('H*', $token) . pack('n', strlen($payload)) . $payload
It should look like this :
$msg = chr(1) . pack("N", $msg_id) . pack("N", $expiry) . pack('n', 32) . pack('H*', $token) . pack('n', strlen($payload)) . $payload;
Not sure if it's the same issue as iPhone SDK Push notification randomly fails . But some notifications do arrive, I do have does "unpack" warnings when upacking command 8 packets.
I have a php file that pulls any notifications from a table (DB) which has token, payload, and device (iphone,ipad). This php runs every 15 seconds on the server cron. I don't know why 20% of the notifications arrive sometimes.... sometimes 80% arrive.... and sometime none arrive (I have 10 devices for testing). I read that apples notifications are "best effort" and that there are not guaranteed to arrive but this is ridiculous, something must be wrong with the logic of my code. Here is my code, I have changed names of directories, DB info and files for privacy reasons. Please help.
<?php
include("/#####/DB.conf");
$link = mysql_connect($host,$userDB,$passDB) or die("Failed to connect: " . mysql_error());
mysql_select_db('#######') or die("Failed to connect: " . mysql_error());
mysql_query("SET NAMES 'utf8'");
$certFile_iphone = '/##########/push_production.pem';
$certFile_ipad = '/########/push_production.pem';
$passphrase = '#########';
$sent += sendPush($certFile_iphone,$passphrase,'iphone');
$borrados += checkFeedback($certFile_iphone,$passphrase,'iphone');
$sent += sendPush($certFile_ipad,$passphrase,'ipad');
$borrados += checkFeedback($certFile_ipad,$passphrase,'ipad');
//////////////////////////////
//Send notification to users//
//////////////////////////////
function sendPush($cert,$pass,$device)
{
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $cert);
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
// Open a connection to the APNS server
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 600, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
//gets all the devices of that type and their current notification to send
$query = mysql_query("SELECT * FROM pushNotification_buffer WHERE device = '".$device."' LIMIT 1000;");
$errors = 0;
$sent = 0;
$i = 0;
while($row = mysql_fetch_array($query))
{
$payload = $row['payload'];
$deviceToken = $row['identificador'];
//$msg = chr(0) . pack('n', 32) . pack('H*', $row['identificador']) . pack('n', strlen($payload)) . $payload;
$msgInner =
chr(1)
. pack('n', 32)
. pack('H*', $deviceToken)
. chr(2)
. pack('n', strlen($payload))
. $payload
. chr(3)
. pack('n', 4)
. chr($i)
. chr(4)
. pack('n', 4)
. pack('N', time() + 86400)
. chr(5)
. pack('n', 1)
. chr(10)
;
$msg=
chr(2)
. pack('N', strlen($msgInner))
. $msgInner
;
$i++;
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
{
$errors++;
usleep(1000000);
$result = fwrite($fp, $msg, strlen($msg));
error_log("Error on Device (2): " . $row['identificador']);
}
if (!$result)
{
$errors++;
fclose($fp);
return $sent;
}
else
{
$sent++;
mysql_query("DELETE FROM pushNotification_buffer WHERE identificador = '".$row['identificador']."' AND payload = '".mysql_real_escape_string($row['payload'])."' LIMIT 1;");
}
error_response($fp);
}
fclose($fp);
return $sent;
}
/////////////////
//APNS FeedBack//
/////////////////
function checkFeedback($cert,$pass,$device){
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $cert);
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$fp = stream_socket_client('ssl://feedback.push.apple.com:2196', $error, $errorString, 600, STREAM_CLIENT_CONNECT, $ctx);
$toDelete = 0;
if(!$fp) echo ("NOTICE: Failed to connect to device: $error - $errorString.");
while ($devcon = fread($fp, 38)){
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$token = substr($rawhex, 12, 64);
if(!empty($token))
{
if($device == 'iphone'){
mysql_query("DELETE FROM iphoneDevicesAPNS WHERE identificador = '" . $token . "' LIMIT 1;");
}else{
mysql_query("DELETE FROM ipadDevicesAPNS WHERE identificador = '" . $token . "' LIMIT 1;");
}
$toDelete++;
}
}
fclose($fp);
return $toDelete;
}
/////////////////////////////
//Error response, command 8//
/////////////////////////////
function error_response($fp){
$read = array($fp);
$null = null;
$changedStreams = stream_select($read, $null, $null, 0, 1000000);
if ($changedStreams === false)
{
echo ("Error: Unabled to wait for a stream availability");
}
elseif ($changedStreams > 0){
$responseBinary = fread($fp, 6);
if ($responseBinary !== false || strlen($responseBinary) == 6){
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
var_dump($response);
if (strlen($response["command"] > 0) || strlen($response["status_code"] > 0) || strlen($response["identifier"] > 0)){
$texto=
"------------------------------------------". "\n".date("Y-m-d G:i:s"). "\n";
"id: " . $response["identifier"] . "\n" .
"code: ". $response["status_code"] . "\n" .
"command: ". $response["command"] . "\n". "\n"
;
file_put_contents ( "/#####/logAPNS.log" ,$texto, FILE_APPEND);
}
}
}
}
?>
I do have does "unpack" warnings when upacking command 8 packets
This implies that you are getting error responses from Apple, but from what I see in the code, you don't do anything with these errors beside logging them. Each such error response means that you sent invalid data to Apple, and as a result Apple closed the connection. This means any message sent after the invalid one will be discarded until you open a new connection.
The proper way to handle such responses is to parse the bad message ID contained in them, open a new connection to APNS, and re-send all the messages that you sent after the bad message.
The most likely error is invalid device token. That usually happen when you send a development token to production push environment or vice versa. If you clear the invalid device tokens from your DB, you are likely to see 100% of the messages arriving.
Apple's docs say that:
"If you send a notification and APNs finds the notification malformed or otherwise unintelligible, it returns an error-response packet prior to disconnecting. (If there is no error, APNs doesn’t return anything.) Figure 5-3 depicts the format of the error-response packet."
This leads me to believe that the only reason that APNS would not send something back is if what I sent them was in the correct format. However when I try to fread for their response I get a string of 0 length and when unpacked becomes null, which I assume means nothing has been written back to me.
I the stream was opened by stream_socket_client() and did not return false or throw an exception. I know that my fwrite successfully wrote 154 bytes to that stream as well. Why would there be no response from Apple?
Here is the code for connecting to APNS:
function openConnection() {
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $this->APNS_CERT);
stream_context_set_option($streamContext, 'ssl', 'passphrase', $this->Password);
$apns = stream_socket_client('ssl://' . $this -> APNS_HOST . ':' . $this -> APNS_PORT, $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
return $apns;
}
Then, in the next method after calling openConnection:
//open the connection to use with apple.
$apns = $this->openConnection();
$alert = $message['alert'];
$badge = $message['badge'];
$deviceToken = $message['deviceToken'];
$payload['aps'] = array(
'alert' => $message['alert'],
'badge' => $message['badge'],
'sound' => $message['sound']
);
if ($message['extraPayload'] != null) {
$payload['acme'] = $message['extraPayload'];
}
$encodedString = json_encode($payload);
//create message
$apnsMessage = chr(1) . pack("N", $message['identifier']) . pack("N", $message['expire']) . pack("n", 32) . pack('H*', str_replace(' ', '', $message['deviceToken'])) . pack("n",strlen($encodedString)) . $encodedString;
$write = fwrite($apns, $apnsMessage);
echo $write;
//the echo was just to see if it wrote.
if (!$apns) {
socket_close($apns);
fclose($apns);
echo "connection to APNS was lost.";
}
//look for changes. $null=null because some bug doesn't just let you pass null.
$null = null;
$changedStreams = stream_select($streamArray, $null, $null, 0, 1000000);
//check if it is actually false
if ($changedStreams === false) {
//close stream when done.
socket_close($apns);
fclose($apns);
echo "No response from APNs";
} elseif ($changedStreams > 0) {
//then check if what they sent back is an error and grab the error packet
$responseBinary = fread($apns, 6);
var_dump($responseBinary);
//check that it's the right thing
if ($responseBinary != false || strlen($responseBinary) == 6) {
//convert it from it's binary stream state and print.
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
var_dump($response);
//close stream when done.
socket_close($apns);
fclose($apns);
}
} else {
echo "Apple failed to respond, message was not sent.";
}
The var_dump at the end is NULL.
Edit:
Turns out it was an error with conflicting credentials. It was solved by creating a new pem file.
From that page:
You should regularly connect with the feedback web server and fetch the current list of those devices that have repeatedly reported failed-delivery attempts. Then you should cease sending notifications to the devices associated with those applications. See “The Feedback Service” for more information.
Feedback Service
Access to the feedback service takes place through a binary interface similar to that used for sending push notifications. You access the production feedback service via feedback.push.apple.com, port 2196; you access the sandbox feedback service via feedback.sandbox.push.apple.com, port 2196. As with the binary interface for push notifications, you must use TLS (or SSL) to establish a secured communications channel. The SSL certificate required for these connections is the same one that is provisioned for sending notifications. To establish a trusted provider identity, you should present this certificate to APNs at connection time using peer-to-peer authentication.
I successfully manage to implement an iPhone push notification server (PHP) last year; I had to change the server, and was thinking that moving files was sufficient... I was wrong, since the modification notifications are not sent anymore. There's no error, everything seems ok, but notification aren't received.
Below is my server code; anyone can think of a cause, or a way to find the problem ? (notes: the $deviceTokens var is correct, contains the device tokens, and I've successfully tested my .pem certificate with an openssl command).
$payload['aps'] = array('alert' => 'notification!!', 'sound' => 'push.aif');
$payload = json_encode($payload);
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', 'libraries/ck_prod.pem');
$apns = stream_socket_client('ssl://gateway.sandbox.push.apple.com:' . 2195, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
if($error) {
log_message('error', $errorString);
return;
}
log_message('debug', 'sending push notification...');
if($apns) {
foreach($deviceTokens as $deviceToken) {
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);
}
fclose($apns);
} else {
log_message('error', 'error while sending push notification');
}
Well well... Maybe I should have notice that I was contacting the test server (gateway.sandbox.push.apple.com)... Some days are just difficult...