├── README.md └── application ├── config └── apn.php └── library └── apn.php /README.md: -------------------------------------------------------------------------------- 1 | Codeigniter-apns 2 | (c) 2012, Anton Gorodezkiy 3 | 4 | Это библиотека для codeigniter для взаимодействия с Apple Push Notifications Service 5 | Библиотека основана на коде APNS Copyright (c) 2010 Benjamin Ortuzar Seconde 6 | 7 | Пример базового использования (controller): 8 | 9 | function send_notifications() 10 | { 11 | $this->load->library('apn'); 12 | $this->apn->payloadMethod = 'enhance'; // включите этот метод для отладки 13 | $this->apn->connectToPush(); 14 | 15 | // добавление собственных переменных в notification 16 | $this->apn->setData(array( 'someKey' => true )); 17 | 18 | $send_result = $this->apn->sendMessage($device_token, 'Тестовое уведомление #1 (TIME:'.date('H:i:s').')', /*badge*/ 2, /*sound*/ 'default' ); 19 | 20 | if($send_result) 21 | log_message('debug','Отправлено успешно'); 22 | else 23 | log_message('error',$this->apn->error); 24 | 25 | 26 | $this->apn->disconnectPush(); 27 | } 28 | 29 | // для получения идентификаторов устройств, на которых приложение больше не установлено 30 | public function apn_feedback() 31 | { 32 | $this->load->library('apn'); 33 | 34 | $unactive = $this->apn->getFeedbackTokens(); 35 | 36 | if (!count($unactive)) 37 | { 38 | log_message('info','Feedback: No devices found. Stopping.'); 39 | return false; 40 | } 41 | 42 | foreach($unactive as $u) 43 | { 44 | $devices_tokens[] = $u['devtoken']; 45 | } 46 | 47 | /* 48 | print_r($unactive) -> Array ( [0] => Array ( [timestamp] => 1340270617 [length] => 32 [devtoken] => 002bdf9985984f0b774e78f256eb6e6c6e5c576d3a0c8f1fd8ef9eb2c4499cb4 ) ) 49 | */ 50 | } 51 | 52 | -------------------------------------- 53 | 54 | Codeigniter-apns 55 | (c) 2012, Anton Gorodezkiy 56 | 57 | This is codeigniter library to work with Apple Push Notifications Service 58 | It based on APNS Copyright (c) 2010 Benjamin Ortuzar Seconde 59 | 60 | Basic usage for pushing (controller): 61 | 62 | function send_notifications() 63 | { 64 | $this->load->library('apn'); 65 | $this->apn->payloadMethod = 'enhance'; // you can turn on this method for debuggin purpose 66 | $this->apn->connectToPush(); 67 | 68 | // adding custom variables to the notification 69 | $this->apn->setData(array( 'someKey' => true )); 70 | 71 | $send_result = $this->apn->sendMessage($device_token, 'Test notif #1 (TIME:'.date('H:i:s').')', /*badge*/ 2, /*sound*/ 'default' ); 72 | 73 | if($send_result) 74 | log_message('debug','Sending successful'); 75 | else 76 | log_message('error',$this->apn->error); 77 | 78 | 79 | $this->apn->disconnectPush(); 80 | } 81 | 82 | // designed for retreiving devices, on which app not installed anymore 83 | public function apn_feedback() 84 | { 85 | $this->load->library('apn'); 86 | 87 | $unactive = $this->apn->getFeedbackTokens(); 88 | 89 | if (!count($unactive)) 90 | { 91 | log_message('info','Feedback: No devices found. Stopping.'); 92 | return false; 93 | } 94 | 95 | foreach($unactive as $u) 96 | { 97 | $devices_tokens[] = $u['devtoken']; 98 | } 99 | 100 | /* 101 | print_r($unactive) -> Array ( [0] => Array ( [timestamp] => 1340270617 [length] => 32 [devtoken] => 002bdf9985984f0b774e78f256eb6e6c6e5c576d3a0c8f1fd8ef9eb2c4499cb4 ) ) 102 | */ 103 | } 104 | -------------------------------------------------------------------------------- /application/config/apn.php: -------------------------------------------------------------------------------- 1 | 5 | ## 6 | ## This file is part of APNS. 7 | ## 8 | ## APNS is free software: you can redistribute it and/or modify 9 | ## it under the terms of the GNU Lesser General Public License as 10 | ## published by the Free Software Foundation, either version 3 of 11 | ## the License, or (at your option) any later version. 12 | ## 13 | ## APNS is distributed in the hope that it will be useful, 14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ## GNU General Public License for more details. 17 | ## 18 | ## You should have received a copy of the GNU General Public License 19 | ## along with APNS. If not, see . 20 | ## 21 | ## 22 | ## $Id: Apns.php 168 2010-08-28 01:24:04Z Benjamin Ortuzar Seconde $ 23 | ## 24 | ####################################################################### 25 | /* 26 | * Modified and adapted for Codeigniter 27 | * (c) Anton_Gorodezkiy, antongorodezkiy@gmail.com 28 | * 2012 29 | */ 30 | 31 | /** 32 | * Apple Push Notification Server 33 | */ 34 | class APN 35 | { 36 | 37 | /******************************* 38 | PROTECTED : */ 39 | 40 | 41 | 42 | protected $server; 43 | protected $keyCertFilePath; 44 | protected $passphrase; 45 | protected $pushStream; 46 | protected $feedbackStream; 47 | protected $timeout; 48 | protected $idCounter = 0; 49 | protected $expiry; 50 | protected $allowReconnect = true; 51 | protected $additionalData = array(); 52 | protected $apnResonses = array( 53 | 0 => 'No errors encountered', 54 | 1 => 'Processing error', 55 | 2 => 'Missing device token', 56 | 3 => 'Missing topic', 57 | 4 => 'Missing payload', 58 | 5 => 'Invalid token size', 59 | 6 => 'Invalid topic size', 60 | 7 => 'Invalid payload size', 61 | 8 => 'Invalid token', 62 | 255 => 'None (unknown)', 63 | ); 64 | 65 | private $connection_start; 66 | 67 | public $error; 68 | public $payloadMethod = 'simple'; 69 | 70 | /** 71 | * Connects to the server with the certificate and passphrase 72 | * 73 | * @return 74 | */ 75 | protected function connect($server) { 76 | 77 | $ctx = stream_context_create(); 78 | stream_context_set_option($ctx, 'ssl', 'local_cert', $this->keyCertFilePath); 79 | stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase); 80 | 81 | $stream = stream_socket_client($server, $err, $errstr, $this->timeout, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx); 82 | log_message('debug',"APN: Maybe some errors: $err: $errstr"); 83 | 84 | 85 | if (!$stream) { 86 | 87 | if ($err) 88 | show_error("APN Failed to connect: $err $errstr"); 89 | else 90 | show_error("APN Failed to connect: Something wrong with context"); 91 | 92 | return false; 93 | } 94 | else { 95 | stream_set_timeout($stream,20); 96 | log_message('debug',"APN: Opening connection to: {$server}"); 97 | return $stream; 98 | } 99 | } 100 | 101 | 102 | 103 | /** 104 | * Generates the payload 105 | * 106 | * @param $message 107 | * @param $badge 108 | * @param $sound 109 | * @return 110 | */ 111 | protected function generatePayload($message, $badge = NULL, $sound = NULL, $newstand = false) { 112 | 113 | $body = array(); 114 | 115 | // additional data 116 | if (is_array($this->additionalData) && count($this->additionalData)) 117 | { 118 | $body = $this->additionalData; 119 | } 120 | 121 | //message 122 | $body['aps'] = array('alert' => $message); 123 | 124 | //badge 125 | if ($badge) 126 | $body['aps']['badge'] = $badge; 127 | 128 | if ($badge == 'clear') 129 | $body['aps']['badge'] = 0; 130 | 131 | //sound 132 | if ($sound) 133 | $body['aps']['sound'] = $sound; 134 | 135 | //newstand content-available 136 | if($newstand) 137 | $body['aps']['content-available'] = 1; 138 | 139 | 140 | $payload = json_encode($body); 141 | log_message('debug',"APN: generatePayload '$payload'"); 142 | return $payload; 143 | } 144 | 145 | 146 | 147 | /** 148 | * Writes the contents of payload to the file stream 149 | * 150 | * @param $deviceToken 151 | * @param $payload 152 | */ 153 | protected function sendPayloadSimple($deviceToken, $payload){ 154 | 155 | $this->idCounter++; 156 | 157 | log_message('debug',"APN: sendPayloadSimple to '$deviceToken'"); 158 | 159 | $msg = chr(0) // command 160 | . pack('n',32) // token length 161 | . pack('H*', $deviceToken) // device token 162 | . pack('n',strlen($payload)) // payload length 163 | . $payload; // payload 164 | 165 | log_message('debug',"APN: payload: '$msg'"); 166 | log_message('debug',"APN: payload length: '".strlen($msg)."'"); 167 | $result = fwrite($this->pushStream, $msg, strlen($msg)); 168 | 169 | if ($result) 170 | return true; 171 | else 172 | return false; 173 | } 174 | 175 | 176 | /** 177 | * Writes the contents of payload to the file stream with enhanced api (expiry, debug) 178 | * 179 | * @param $deviceToken 180 | * @param $payload 181 | */ 182 | protected function sendPayloadEnhance($deviceToken, $payload, $expiry = 86400) { 183 | 184 | if (!is_resource($this->pushStream)) 185 | $this->reconnectPush(); 186 | 187 | $this->idCounter++; 188 | 189 | log_message('debug',"APN: sendPayloadEnhance to '$deviceToken'"); 190 | 191 | $payload_length = strlen($payload); 192 | 193 | $request = chr(1) // command 194 | . pack("N", time()) // identifier 195 | . pack("N", time() + $expiry) // expiry 196 | . pack('n', 32) // token length 197 | . pack('H*', $deviceToken) // device token 198 | . pack('n', $payload_length) // payload length 199 | . $payload; 200 | 201 | $request_unpacked = @unpack('Ccommand/Nidentifier/Nexpiry/ntoken_length/H64device_token/npayload_length/A*payload', $request); // payload 202 | 203 | log_message('debug', "APN: request: '$request'"); 204 | log_message('debug', "APN: unpacked request: '" . print_r($request_unpacked, true) . "'"); 205 | log_message('debug', "APN: payload length: '" . $payload_length . "'"); 206 | $result = fwrite($this->pushStream, $request, strlen($request)); 207 | 208 | if ($result) 209 | { 210 | return $this->getPayloadStatuses(); 211 | } 212 | 213 | return false; 214 | } 215 | 216 | 217 | protected function timeoutSoon($left_seconds = 5) 218 | { 219 | $t = ( (round(microtime(true) - $this->connection_start) >= ($this->timeout - $left_seconds))); 220 | return (bool)$t; 221 | } 222 | 223 | 224 | 225 | /* PROTECTED ^ 226 | *******************************/ 227 | 228 | 229 | /** 230 | * Connects to the APNS server with a certificate and a passphrase 231 | * 232 | * @param $server 233 | * @param $keyCertFilePath 234 | * @param $passphrase 235 | */ 236 | function __construct() { 237 | 238 | $this->_ci = get_instance(); 239 | 240 | $this->_ci->config->load('apn',true); 241 | 242 | 243 | if(!file_exists($this->_ci->config->item('PermissionFile','apn'))) 244 | { 245 | show_error("APN Failed to connect: APN Permission file not found"); 246 | } 247 | 248 | $this->pushServer = $this->_ci->config->item('Sandbox','apn') ? $this->_ci->config->item('PushGatewaySandbox','apn') : $this->_ci->config->item('PushGateway','apn'); 249 | $this->feedbackServer = $this->_ci->config->item('Sandbox','apn') ? $this->_ci->config->item('FeedbackGatewaySandbox','apn') : $this->_ci->config->item('FeedbackGateway','apn'); 250 | 251 | $this->keyCertFilePath = $this->_ci->config->item('PermissionFile','apn'); 252 | $this->passphrase = $this->_ci->config->item('PassPhrase','apn'); 253 | $this->timeout = $this->_ci->config->item('Timeout','apn') ? $this->_ci->config->item('Timeout','apn') : 60; 254 | $this->expiry = $this->_ci->config->item('Expiry','apn') ? $this->_ci->config->item('Expiry','apn') : 86400; 255 | } 256 | 257 | 258 | 259 | 260 | /** 261 | * Public connector to push service 262 | */ 263 | public function connectToPush() 264 | { 265 | if (!$this->pushStream or !is_resource($this->pushStream)) 266 | { 267 | log_message('debug',"APN: connectToPush"); 268 | 269 | $this->pushStream = $this->connect($this->pushServer); 270 | 271 | if ($this->pushStream) 272 | { 273 | $this->connection_start = microtime(true); 274 | //stream_set_blocking($this->pushStream,0); 275 | } 276 | } 277 | 278 | return $this->pushStream; 279 | } 280 | 281 | /** 282 | * Public connector to feedback service 283 | */ 284 | public function connectToFeedback() 285 | { 286 | log_message('info',"APN: connectToFeedback"); 287 | return $this->feedbackStream = $this->connect($this->feedbackServer); 288 | } 289 | 290 | /** 291 | * Public diconnector to push service 292 | */ 293 | function disconnectPush() 294 | { 295 | log_message('debug',"APN: disconnectPush"); 296 | if ($this->pushStream && is_resource($this->pushStream)) 297 | { 298 | $this->connection_start = 0; 299 | return @fclose($this->pushStream); 300 | } 301 | else 302 | return true; 303 | } 304 | 305 | /** 306 | * Public disconnector to feedback service 307 | */ 308 | function disconnectFeedback() 309 | { 310 | log_message('info',"APN: disconnectFeedback"); 311 | if ($this->feedbackStream && is_resource($this->feedbackStream)) 312 | return @fclose($this->feedbackStream); 313 | else 314 | return true; 315 | } 316 | 317 | function reconnectPush() 318 | { 319 | $this->disconnectPush(); 320 | 321 | if ($this->connectToPush()) 322 | { 323 | log_message('debug',"APN: reconnect"); 324 | return true; 325 | } 326 | else 327 | { 328 | log_message('debug',"APN: cannot reconnect"); 329 | return false; 330 | } 331 | } 332 | 333 | function tryReconnectPush() 334 | { 335 | if ($this->allowReconnect) 336 | { 337 | if($this->timeoutSoon()) 338 | { 339 | return $this->reconnectPush(); 340 | } 341 | } 342 | 343 | return false; 344 | } 345 | 346 | 347 | /** 348 | * Sends a message to device 349 | * 350 | * @param $deviceToken 351 | * @param $message 352 | * @param $badge 353 | * @param $sound 354 | */ 355 | public function sendMessage($deviceToken, $message, $badge = NULL, $sound = NULL, $expiry = '', $newstand = false) 356 | { 357 | $this->error = ''; 358 | 359 | if (!ctype_xdigit($deviceToken)) 360 | { 361 | log_message('debug',"APN: Error - '$deviceToken' token is invalid. Provided device token contains not hexadecimal chars"); 362 | $this->error = 'Invalid device token. Provided device token contains not hexadecimal chars'; 363 | return false; 364 | } 365 | 366 | // restart the connection 367 | $this->tryReconnectPush(); 368 | 369 | log_message('info',"APN: sendMessage '$message' to $deviceToken"); 370 | 371 | //generate the payload 372 | $payload = $this->generatePayload($message, $badge, $sound, $newstand); 373 | 374 | $deviceToken = str_replace(' ', '', $deviceToken); 375 | 376 | //send payload to the device. 377 | if ($this->payloadMethod == 'simple') 378 | $this->sendPayloadSimple($deviceToken, $payload); 379 | else 380 | { 381 | if (!$expiry) 382 | $expiry = $this->expiry; 383 | 384 | return $this->sendPayloadEnhance($deviceToken, $payload, $expiry); 385 | } 386 | } 387 | 388 | 389 | /** 390 | * Writes the contents of payload to the file stream 391 | * 392 | * @param $deviceToken 393 | * @param $payload 394 | * @return 395 | */ 396 | function getPayloadStatuses() 397 | { 398 | 399 | $read = array($this->pushStream); 400 | $null = null; 401 | $changedStreams = stream_select($read, $null, $null, 0, 2000000); 402 | 403 | if ($changedStreams === false) 404 | { 405 | log_message('error',"APN Error: Unabled to wait for a stream availability"); 406 | } 407 | elseif ($changedStreams > 0) 408 | { 409 | 410 | $responseBinary = fread($this->pushStream, 6); 411 | if ($responseBinary !== false || strlen($responseBinary) == 6) { 412 | 413 | if (!$responseBinary) 414 | return true; 415 | 416 | $response = @unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary); 417 | 418 | log_message('debug','APN: debugPayload response - '.print_r($response,true)); 419 | 420 | if ($response && $response['status_code'] > 0) 421 | { 422 | log_message('error','APN: debugPayload response - status_code:'.$response['status_code'].' => '.$this->apnResonses[$response['status_code']]); 423 | $this->error = $this->apnResonses[$response['status_code']]; 424 | return false; 425 | } 426 | else 427 | { 428 | if (isset($response['status_code'])) 429 | log_message('debug','APN: debugPayload response - '.print_r($response['status_code'],true)); 430 | } 431 | 432 | } 433 | else 434 | { 435 | log_message('debug',"APN: responseBinary = $responseBinary"); 436 | return false; 437 | } 438 | } 439 | else 440 | log_message('debug',"APN: No streams to change, $changedStreams"); 441 | 442 | return true; 443 | } 444 | 445 | 446 | 447 | /** 448 | * Gets an array of feedback tokens 449 | * 450 | * @return 451 | */ 452 | public function getFeedbackTokens() { 453 | 454 | log_message('debug',"APN: getFeedbackTokens {$this->feedbackStream}"); 455 | $this->connectToFeedback(); 456 | 457 | $feedback_tokens = array(); 458 | //and read the data on the connection: 459 | while(!feof($this->feedbackStream)) { 460 | $data = fread($this->feedbackStream, 38); 461 | if(strlen($data)) { 462 | //echo $data; 463 | $feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data); 464 | } 465 | } 466 | 467 | $this->disconnectFeedback(); 468 | 469 | return $feedback_tokens; 470 | } 471 | 472 | 473 | /** 474 | * Sets additional data which will be send with main apn message 475 | * 476 | * @param $data 477 | * @return 478 | */ 479 | public function setData($data) 480 | { 481 | if (!is_array($data)) 482 | { 483 | log_message('error',"APN: cannot add additional data - not an array"); 484 | return false; 485 | } 486 | 487 | if (isset($data['apn'])) 488 | { 489 | log_message('error',"APN: cannot add additional data - key 'apn' is reserved"); 490 | return false; 491 | } 492 | 493 | return $this->additionalData = $data; 494 | } 495 | 496 | 497 | 498 | /** 499 | * Closes the stream 500 | */ 501 | function __destruct(){ 502 | $this->disconnectPush(); 503 | $this->disconnectFeedback(); 504 | } 505 | 506 | }//end of class 507 | 508 | 509 | --------------------------------------------------------------------------------