Using PHP to send iOS Push Notifications via APNs
I have implemented Push-Notifications into my Project and everything works fine so far. I’ve tried sending Notifications through Pusher and this worked out just fine. But I have to send them through PHP, which isn’t working yet. I found many old explanations on how to make this happen, but none of them seem to work for me. This is what I’m trying to work with:
// APNs Push testen auf Token $deviceToken = $_GET['key']; // Device-Token // Payload erstellen und JSON codieren $payload['aps'] = array('alert' => 'TitleText', 'badge' => 1, 'sound' => 'default'); $payload = json_encode($payload); $apnsHost = 'gateway.sandbox.push.apple.com'; $apnsPort = 2195; $apnsCert = 'certificate.pem'; // Stream erstellen $streamContext = stream_context_create(); stream_context_set_option($streamContext, 'ssl', 'certificate.cer', $apnsCert); $apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext); if ($apns) < // Nachricht erstellen und senden $apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($payload)) . $payload; fwrite($apns, $apnsMessage); // Verbindung schliessen fclose($apns); >else
How can i add more Params/Data to the Notification, for example some Action-id so when the user open the notification the App will get that Action-id to perform some function related to it.
Сервис push-уведомлений Pushover для Android и iOS в связке с PHP
Вкратце, push-уведомления — это небольшие по объему важные сообщения от программы или сервиса, отображаемые операционной системой тогда, когда вы непосредственно не работаете с указанным приложением или сервисом. Преимущество таких уведомлений в отсутствии необходимости держать программу вечно в памяти, тратя на нее процессорные мощности и память.
Не буду здесь расписывать всю технологию доставки удаленного уведомления, ибо это уже сделано до меня. Выглядит примерно так: периодически демон опрашивает сервер и в случае появления сообщения, показывает его нам.
Для iOS придумали APNS, для Android-а — C2DM-GCM, я же хочу рассказать про кроссплатформенный (громко) сервис Pushover и связке его с php-сайтом.
Пример задачи
Предположим, что раз в день мы хотим знать что-либо о количестве заказов на сайте за день и их стоимости.
Сайт крутится на некоторой CMS на PHP и mySQL, принимающая сторона имеет стильные iPhone и Android-телефоны.
Срочность доставки сообщения не относится к жизненно-важным показателям.
Надо найти условно безгеморройное решение.
Pushover
Pushover — это скромный сервис уведомлений, а также приложения для iOS и Android, планируются поделки и для BlackBerry и OS X Mountain Lion. Сервис имеет свой API, позволяет отправлять бесплатно до 7.5 тысяч сообщений в месяц.
Сообщение, помимо основного текста сообщения длиной 512 символов, может содержать крупный заголовок, URL (тогда длина сообщения увеличивается до 500) и его тайтл (все отображается отдельными сформированными блоками, потому такое разграничение). Сообщение можно доставить под неким выбранным указанным приоритетом. Пользователь может указать «тихие» часы, когда его не стоить будить уведомления, а также подключать и отключать устройства, на которые будут приходить уведомления.
Уведомление может быть доставлено пользователям, предоставившим свой код, всем устройствам этого пользователя или по выбору. Для приема сообщения пользователю необходимо быть зарегистрированным в сервисе и обладать хотя бы одним рабочим устройством.
Добавление пользователя
После прохождения регистрации, каждый пользователь попадает в свой кабинет, где он сразу видит свой хэш-токен. Это уникальный идентификатор пользователя, на который в последствии и отправляются уведомления.
Пользователю, желающему принимать сообщения, необходимо поставить на свой телефон/планшет/абы что приложение из соответствующего магазина.
Добавление сервиса
Добавление сервиса ничуть не сложнее. Из личного кабинета надо перейти на страницу создания приложения, где предлагается описать продукт:
После заполнения соответствующих полей, нам становится известен токен приложения. В принципе, это все, что необходимо для отправки сообщения.
На странице приложения в последующем будет красивый график успешно отправленных сообщений.
Связывание приложений и получателей
… не выполняется никак. Любое приложение может отправить любому пользователю уведомление, если знает его токен. Прием токенов от населения остается на совести приложения. Также как и отписка от рассылок.
API
- token — хэш-токен вашего приложения или сервиса.
- user — хэш-токен пользователя, которому вы отправляете уведомление.
- message — текстовая часть сообщения.
- device — идентификатор устройства пользователя, дабы не отсылать уведомления сразу на все его устройства
- title — заголовок сообщения, если не указан, будет показано название сервиса
- url — ссылка на web-страницу, если в этом есть необходимость
- url_title — заголовок к ссылке
- priority — приоритет сообщения, ставится в 1 для высокого приоритета, обходящего все «тихие» часы и -1 для тихого уведомления.
- timestamp — UNIX метка времени, когда это уведомлениебыло создано. Расписания доставки сообщений сервисом не предусмотрено.
Ответ сервера
Ответ сервера будет представлен в json или xml формате (в зависимости от расширения вызываемого скрипта).
Если все прошло удачно будет отдан объект содержанием поля status, равном 1.
Иначе, поле status будет содержать нечто иное, а поле errors — массив описания ошибок. Вот примеры ответов удачной и неудачной отправок в формате XML:
invalid application token is invalid 0
PHP
На главной странице и в факе в разделах «смотрите, как легко!» приводятся коды простейшего скрипта на различных языках для отправки и есть ссылка на 3rd-party php-класс от Chris Schalenborgh.
Везде используется сURL, что впрочем, понятно.
Зафигарим свой класс
Куда ж нынче без велосипедов?
На самом деле, мне не слишком понравилось, что успех отправки уведомления определяется либо как true/false, либо выводится сразу вся простыня ответа сервера. Да вообще обработки ошибок нет. Считаю, что если посетителю сайта не обязательно, то разработчику надо знать, почему не отправлено то или иное сообщение.
В общем, существенно меняем все, классы уехали на GitHub.
class PushoverException extends Exception < /** * Messages array * @var array */ private $fMessages; /** * Exception constructor * @param array $aMessages An array of messages */ public function __construct(array $aMessages) < parent::__construct('PushoverException exception'); $this->fMessages = $aMessages; > /** * Get messages array * @return array */ public function getMessages() < return empty($this->fMessages) ? array() : $this->fMessages; > >
class Pushover < /* * Pushover json api service url */ const C_API_URL = 'https://api.pushover.net/1/messages.json'; /** * Properties storage array * @var array */ private $fProperties; /** * cURL instance */ private $fCurl; //--------------------------------------------------------------------------// /** * Properties getter * @param string $aPropertyName Property name * @return mixed */ public function __get($aPropertyName) < if(array_key_exists($aPropertyName, $this->fProperties)) return $this->fProperties[$aPropertyName]; return null; > /** * Properties setter * @param string $aPropertyName Property name * @param mixed $aValue Property value */ public function __set($aPropertyName, $aValue) < $this->fProperties[$aPropertyName] = $aValue; > //--------------------------------------------------------------------------// /** * Class constructor * @param string $aApplicationToken Application token */ public function __construct($aApplicationToken = null) < if(!empty($aApplicationToken)) $this->applicationToken = $aApplicationToken; $this->fCurl = curl_init(); curl_setopt($this->fCurl, CURLOPT_URL, self::C_API_URL); curl_setopt($this->fCurl, CURLOPT_HEADER, false); curl_setopt($this->fCurl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->fCurl, CURLOPT_SSL_VERIFYPEER, false); > /** * Class destructor */ public function __destruct() < curl_close($this->fCurl); > //--------------------------------------------------------------------------// /** * Throws an exceprion with single message * @param mixed $aMessage * @throws PushoverException */ public function throwMessage($aMessage) < throw new PushoverException(array($aMessage)); >/** * Throws an exceprion with an array of messages * @param array $aMessages * @throws PushoverException */ public function throwMessages(array $aMessages) < throw new PushoverException($aMessages); >//--------------------------------------------------------------------------// /** * Send pushover notification */ public function send() < if(!strlen($this->applicationToken)) $this->throwMessage('Application token is empty'); if(!strlen($this->userToken)) $this->throwMessage('User token is empty'); if(!strlen($this->notificationMessage)) $this->throwMessage('Notification message is empty'); if(intval($this->notificationTimestamp) notificationTimestamp = time(); $lSendParams = array( 'token' => $this->applicationToken, 'user' => $this->userToken, 'device' => $this->userDevice, 'title' => $this->notificationTitle, 'message' => $this->notificationMessage, 'priority' => $this->notificationPriority, 'timestamp' => $this->notificationTimestamp, 'url' => $this->notificationUrl, 'url_title' => $this->notificationUrlTitle ); foreach($lSendParams as $lKey => $lParam) if(empty($lParam)) unset($lSendParams[$lKey]); curl_setopt($this->fCurl, CURLOPT_POSTFIELDS, $lSendParams); $lResponseJson = curl_exec($this->fCurl); if($lResponseJson === false) $this->throwMessage('API request error'); $lResponse = json_decode($lResponseJson, true); if(empty($lResponse) || !is_array($lResponse)) $this->throwMessage('Bad API response'); if(!empty($lResponse['errors'])) $this->throwMessages($lResponse['errors']); if(empty($lResponse['status']) || intval($lResponse['status']) != 1) $this->throwMessage('Unknown notification send error'); > >
Минимальное сообщение теперь довольно просто отправить, ошибки можно разбирать:
Пользователь уже принял сообщение.
Итого
Знаем об удобном сервисе удаленных уведомлений, одинаково успешно передающий сообщения пользователям Android и iOS.
Имеем рабочий механизм отправки уведомлений с сайта на PHP.
Send iOS Push notification in php with .p8 file
Apple has updated their push notification service and the certificate file received is now a .p8 file. There are many examples online of how to send a push notification with the .pem file but I can’t find anything for a .p8 file. Does anyone have any code that works with the .p8 file?
3 Answers 3
With the script below I’m able to send token-based push notifications with the .p8 file.
The minimum version of curl supporting this is 7.38.0, and it must be compiled with the flag —with-nghttp2 and openssl >= 1.0.2
>'; $key = openssl_pkey_get_private('file://'.$keyfile); $header = ['alg'=>'ES256','kid'=>$keyid]; $claims = ['iss'=>$teamid,'iat'=>time()]; $header_encoded = base64($header); $claims_encoded = base64($claims); $signature = ''; openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256'); $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature); // only needed for PHP prior to 5.5.24 if (!defined('CURL_HTTP_VERSION_2_0')) < define('CURL_HTTP_VERSION_2_0', 3); >$http2ch = curl_init(); curl_setopt_array($http2ch, array( CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, CURLOPT_URL => "$url/3/device/$token", CURLOPT_PORT => 443, CURLOPT_HTTPHEADER => array( "apns-topic: ", "authorization: bearer $jwt" ), CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $message, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_TIMEOUT => 30, CURLOPT_HEADER => 1 )); $result = curl_exec($http2ch); if ($result === FALSE) < throw new Exception("Curl failed: ".curl_error($http2ch)); >$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE); echo $status; function base64($data) < return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '='); >?>