<?php /* * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * and is licensed under the MIT license. */ namespace ZfrPusher\Service; use Guzzle\Http\Exception\BadResponseException; use Guzzle\Plugin\Async\AsyncPlugin; use ZfrPusher\Client\PusherClient; /** * Simple wrapper around PusherClient to simplify its use * * @author Michaƫl Gallego <mic.gallego@gmail.com> * @licence MIT */ class PusherService { /** * Pusher client * * @var PusherClient */ protected $client; /** * Guzzle plugin used to perform asynchronous requests * * @var AsyncPlugin */ protected $asyncPlugin; /** * Constructor * * @param PusherClient $client * @param AsyncPlugin $async */ public function __construct(PusherClient $client, AsyncPlugin $async = null) { $this->client = $client; $this->asyncPlugin = $async ?: new AsyncPlugin(); } /** * Get the Pusher client object * * @return PusherClient */ public function getClient() { return $this->client; } /** * ---------------------------------------------------------------------------------------------------- * EVENTS * ---------------------------------------------------------------------------------------------------- */ /** * Trigger a new event * * @link http://pusher.com/docs/rest_api#method-post-event * @param array|string $channels Single or list of channels * @param string $event Event name * @param array $data Event data (limited to 10 Kb) * @param string $socketId Exclude a specific socket id from the event * @param bool $async If true, the request is performed asynchronously * @return void */ public function trigger($channels, $event, array $data = array(), $socketId = null, $async = false) { $parameters = array( 'event' => $event, 'data' => $data ); if (is_string($channels)) { $parameters['channel'] = $channels; } elseif (is_array($channels)) { $parameters['channels'] = $channels; } if(!empty($socketId)){ $parameters['socket_id'] = $socketId; } if ($async) { $this->client->addSubscriber($this->asyncPlugin); } try { $this->client->trigger($parameters); } catch (BadResponseException $exception) { $this->handleException($exception); } // We need to remove the async plugin in case the user call other functions from the client $this->client->getEventDispatcher()->removeSubscriber($this->asyncPlugin); } /** * Trigger a new event asynchronously (it just aliases to trigger with last parameter set to true) * * @link http://pusher.com/docs/rest_api#method-post-event * @param array|string $channels Single or list of channels * @param string $event Event name * @param array $data Event data (limited to 10 Kb) * @param string $socketId Exclude a specific socket id from the event * @return void */ public function triggerAsync($channels, $event, array $data = array(), $socketId = null) { $this->trigger($event, $channels, $data, $socketId, true); } /** * ---------------------------------------------------------------------------------------------------- * CHANNELS * ---------------------------------------------------------------------------------------------------- */ /** * Get information about multiple channels, optionally filtered by a prefix * * @link http://pusher.com/docs/rest_api#method-get-channels * @param string $prefix A string used to filter channels by prefix * @param array $info An array that contains valid info to retrieve * @return array */ public function getChannelsInfo($prefix = '', array $info = array()) { try { return $this->client->getChannelsInfo(compact('prefix', 'info'))->toArray(); } catch (BadResponseException $exception) { $this->handleException($exception); } } /** * Get information about a single channel identified by its name * * @link http://pusher.com/docs/rest_api#method-get-channel * @param string $channel A channel name * @param array $info An array that contains valid info to retrieve * @return array */ public function getChannelInfo($channel, array $info = array()) { try { return $this->client->getChannelInfo(compact('channel', 'info'))->toArray(); } catch (BadResponseException $exception) { $this->handleException($exception); } } /** * ---------------------------------------------------------------------------------------------------- * USERS * ---------------------------------------------------------------------------------------------------- */ /** * Get a list of user identifiers that are currently subscribed to a channel identified by its name. Note * that only presence channels (whose name begins by presence-) are allowed here * * @link http://pusher.com/docs/rest_api#method-get-users * @param string $channel A presence channel name * @return array */ public function getPresenceUsers($channel) { try { return $this->client->getPresenceUsers(compact('channel'))->toArray(); } catch (BadResponseException $exception) { $this->handleException($exception); } } /** * ---------------------------------------------------------------------------------------------------- * AUTHENTICATION FOR PRESENCE AND PRIVATE CHANNELS * ---------------------------------------------------------------------------------------------------- */ /** * Authenticate a user (identified by its socket identifier) to a presence channel. This method returns * an array whose key is "auth" and value is the signature. It's up to the user to return this correctly * into a JSON string (typically in a controller) * * @link http://pusher.com/docs/auth_signatures#presence * @param string $channel * @param string $socketId * @param array $data * @return array */ public function authenticatePresence($channel, $socketId, array $data) { $credentials = $this->client->getCredentials(); $signature = $this->client->getSignature(); return $signature->signPresenceChannel($channel, $socketId, $data, $credentials); } /** * Authenticate a user (identified by its socket identifier) to a private channel. This method returns * an array whose key is "auth" and value is the signature. It's up to the user to return this correctly * into a JSON string (typically in a controller) * * @link http://pusher.com/docs/auth_signatures#worked-example * @param string $channel * @param string $socketId * @return array */ public function authenticatePrivate($channel, $socketId) { $credentials = $this->client->getCredentials(); $signature = $this->client->getSignature(); return $signature->signPrivateChannel($channel, $socketId, $credentials); } /** * Authenticate a user to either a presence or private channel based on channel name * * @link http://pusher.com/docs/auth_signatures#presence * @link http://pusher.com/docs/auth_signatures#worked-example * @param string $channel * @param string $socketId * @param array $data * @throws Exception\RuntimeException * @return array */ public function authenticate($channel, $socketId, array $data = array()) { if (substr($channel, 0, 8) === 'private-') { return $this->authenticatePrivate($channel, $socketId); } elseif (substr($channel, 0, 9) === 'presence-') { return $this->authenticatePresence($channel, $socketId, $data); } } /** * Throw specific Pusher exceptions according to the status code * * @param BadResponseException $exception * @throws Exception\UnauthorizedException * @throws Exception\ForbiddenException * @throws Exception\RuntimeException * @throws Exception\UnknownResourceException * @return void */ protected function handleException(BadResponseException $exception) { $response = $exception->getResponse(); if ($response->isSuccessful()) { return; } // Reason is injected into the body content, however we do not really want to output the whole // body content to the user, but rather only the real reason, which is typically the last line // of the body content $body = array_filter(explode(PHP_EOL, $response->getMessage())); $message = end($body); switch($response->getStatusCode()) { case 400: throw new Exception\RuntimeException(sprintf( 'An error occurred while trying to handle your request. Reason: %s', $message ), 400); case 401: throw new Exception\UnauthorizedException(sprintf( 'You are not authorized to perform this action. Reason: %s', $message ), 401); case 403: throw new Exception\ForbiddenException(sprintf( 'You are not allowed to perform this action, your application may be disabled or you may have reached your message quota. Reason: %s', $message ), 403); case 404: throw new Exception\UnknownResourceException(sprintf( 'Resource cannot be found, are you sure it exists? Reason: %s', $message ), 404); default: throw new Exception\RuntimeException(sprintf( 'An unknown error occurred on Pusher side. Reason: %s', $message ), $response->getStatusCode()); } } }