Reputation: 782
I am sending push notifications from php to ios. It is working fine and here is my code:
$passphrase = '';
$badge = 1;
$path = base_path('path/to/certificate.pem');
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $path);
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx
);
if (!$fp) {
self::SavePush($device_token, $message, $device_id, $device_type, $user_id, "pending", "normal", null);
}
//echo 'Connected to APNS' . PHP_EOL;
$body['aps'] = array(
'alert' => $message,
'badge' => $badge,
'sound' => 'default'
);
$payload = json_encode($body);
$msg = chr(0) . pack('n', 32) . pack('H*', $device_token) . pack('n', strlen($payload)) . $payload;
$result = fwrite($fp, $msg, strlen($msg));
//print_r($result);exit;
if (!$result) {
self::SavePush($device_token, $message, $device_id, $device_type, $user_id, "pending", "normal", null, $result);
} else {
self::SavePush($device_token, $message, $device_id, $device_type, $user_id, "sent", "normal", null, $result);
}
fclose($fp);
Now the problem that I am facing is, I cannot determine if a notification fails as the $result
contains an integer in every case, either, success or failure. I have passed a random digit as token and it returns integer like 115 or 65 and it changes, every time. So !$result
wont work. How do I know if notification fails?
Upvotes: 8
Views: 3610
Reputation: 135
class PushApple {
function base64url_encode($data)
{
$b64 = base64_encode($data);
if ($b64 === false) {
return false;
}
$url = strtr($b64, '+/', '-_');
return rtrim($url, '=');
}
function base64url_decode($data, $strict = false)
{
$b64 = strtr($data, '-_', '+/');
return base64_decode($b64, $strict);
}
public function iOSNew($data, $devicetoken) {
//https://jwt.io/ for testing..
//https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns/
$tokenTMP = 'dir.../cert/token_apn';
$domain = 'pl.my.app';
$url = 'https://api.sandbox.push.apple.com:443'; //sandbox
// $url = 'https://api.push.apple.com:443';
//----------------
//For security, APNs requires you to refresh your token regularly. Refresh your token no more than once every 20 minutes and no less than once every 60 minutes.
$token = '';
if(file_exists($tokenTMP))
{
$fileTime = filemtime($tokenTMP);
$maxT = time() - 1800; //max 30 min.
if($fileTime < $maxT)
{
$token = file_get_contents($tokenTMP);
// echo 'I get last generated token..';
}
}
//----------------
if($token == '') //Let's generate new token...
{
// echo 'Generate new token...';
//https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns
$assocToken = array();
$assocToken['alg'] = 'ES256'; //The encryption algorithm you used to encrypt the token. APNs supports only the ES256 algorithm, so set the value of this key to ES256.
$assocToken['kid'] = '******'; //The 10-character Key ID you obtained from your developer account
$assocToken['iss'] = '******'; //The issuer key, the value for which is the 10-character Team ID you use for developing your company’s apps. Obtain this value from your developer account.
$assocToken['iat'] = time(); //The “issued at” time, whose value indicates the time at which this JSON token was generated. Specify the value as the number of seconds since Epoch, in UTC. The value must be no more than one hour from the current time.
$header = '{'
. ' "alg" : "'.$assocToken['alg'].'",'
. ' "kid" : "'.$assocToken['kid'].'"'
. '}';
$payload = '{'
. ' "iss" : "'.$assocToken['iss'].'",'
. ' "iat" : "'.$assocToken['iat'].'"'
. '}';
$headerB64URL = $this->base64url_encode($header);
$payloadB64URL = $this->base64url_encode($payload);
$secret = file_get_contents('dir.../cert/AuthKey_TG%234df%#$%.p8');
$signature = '';
openssl_sign($headerB64URL.'.'.$payloadB64URL, $signature, $secret, \OPENSSL_ALGO_SHA256);
$token = $headerB64URL.'.'.$payloadB64URL.'.'.$this->base64url_encode($signature);
//save to use next time..
$fp = fopen($tokenTMP, 'w');
fwrite($fp, $token);
fclose($fp);
}
echo $url . '/3/device/'.$devicetoken;
$apns_id = '8-4-4-5-12';
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $url . '/3/device/'.$devicetoken,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 1,
CURLOPT_TIMEOUT => 10,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HTTPHEADER => array(
"authorization: bearer ".$token,
// "apns-id: ".$apns_id, //
"apns-push-type: alert",
"apns-expiration: 0",
"apns-priority: 0",
"apns-topic: ".$domain,
),
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_POST => '1',
CURLOPT_POSTFIELDS => '{ "aps" : { "alert" : "Hello" } }' // || $devicetoken
));
$response = curl_exec($curl);
echo 'Error: '.curl_errno($curl).' | '. curl_error($curl).' || ';
echo $response;
$ret = json_decode($response, true);
}
}
Upvotes: 0
Reputation: 11636
my two cents to clear an important point: as Apple states, there is "no guarantee of delivery" as push works in this way.
So You can certainly test for network errors, as other guys have suggested but you have no guarantee notification has been received. To obtain so, the only (long..) way is to trigger an URL request in iOS device when it receives a push. This URL request will update your DB.
Upvotes: 4
Reputation: 337
After you send something to APNS with fwrite
, you'll need to fread
(or stream_get_contents
;)) response. Best way is to use non-blocking connection (stream_set_blocking($fp, 0);
) and wait for response in simple time-limited loop:
$read = '';
$to = time() + 2; // max 2 seconds wait...
while (!feof($fp)) // ...or end of connection
{
$r = stream_get_contents($fp);
if (strlen($r) > 0)
$read .= $r;
if (time() >= $to)
break;
usleep(100000);
}
After that, in $read
you'll have either something or nothing. Something (exactly with 6 bytes) means error ($error = unpack('Ccommand/Cstatus/Nident', $read);
). Nothing (or content shorter/longer than 6 bytes) means successfully sent message.
Upvotes: 4
Reputation: 723
A call to fwrite() will return the number of bytes it successfully sent, or it will return FALSE if there was an error sending. Your $result is changing because it changes with the size of the message you are sending. So knowing that, you can surmise that if $result===FALSE then there was an error and the notification failed. If $result!==FALSE, then the notification was successfully sent. This only verifies that the message was sent over the network. It does not verify that the structure of the message or the validity of the tokens or anything like that.
Now, if you trying to find out if the push notification message itself is valid and that apple accepted and is processing it, then you probably want to CURL and HTTP/2 to do this. Doing a quick google search I came across this site that shows a demo how to do it. I have not tested this site http://coding.tabasoft.it/ios/sending-push-notification-with-http2-and-php/ so cannot tell you if the demo is correct or not. It was just a quick search but might get you on the right track.
Upvotes: 6