Completed
Push — master ( d312aa...bbe9f1 )
by Sam
03:12
created

AbstractClientManager::sendMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Jalle19\StatusManager\Manager;
4
5
use Jalle19\StatusManager\Configuration\Configuration;
6
use Jalle19\StatusManager\Event\Events;
7
use Jalle19\StatusManager\Event\InstanceStatusUpdatesEvent;
8
use Jalle19\StatusManager\Exception\MalformedRequestException;
9
use Jalle19\StatusManager\Exception\UnhandledMessageException;
10
use Jalle19\StatusManager\Exception\UnknownRequestException;
11
use Jalle19\StatusManager\Message\AbstractMessage;
12
use Jalle19\StatusManager\Message\Factory as MessageFactory;
13
use Jalle19\StatusManager\Message\Handler\DelegatesMessagesTrait;
14
use Jalle19\StatusManager\Message\Handler\HandlerInterface;
15
use Jalle19\StatusManager\Message\Request\AuthenticationRequest;
16
use Jalle19\StatusManager\Message\Response\AuthenticationResponse;
17
use Jalle19\StatusManager\Message\StatusUpdatesMessage;
18
use Jalle19\tvheadend\exception\RequestFailedException;
19
use Psr\Log\LoggerInterface;
20
use Ratchet\ConnectionInterface;
21
use Ratchet\MessageComponentInterface;
22
use Symfony\Component\EventDispatcher\EventDispatcher;
23
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
24
25
/**
26
 * Keeps track of connected clients and handles events related to them.
27
 *
28
 * @package   Jalle19\StatusManager
29
 * @copyright Copyright &copy; Sam Stenvall 2016-
30
 * @license   https://www.gnu.org/licenses/gpl.html The GNU General Public License v2.0
31
 */
32
abstract class AbstractClientManager extends AbstractManager implements
33
	MessageComponentInterface,
34
	EventSubscriberInterface,
1 ignored issue
show
Coding Style introduced by
Expected 4 spaces before interface name; 1 found
Loading history...
35
	HandlerInterface
36
{
37
38
	use DelegatesMessagesTrait;
39
40
	/**
41
	 * @var \SplObjectStorage the connected clients
42
	 */
43
	protected $_connectedClients;
44
45
	/**
46
	 * @var \SplObjectStorage the authenticated clients
47
	 */
48
	protected $_authenticatedClients;
49
50
51
	/**
52
	 * WebSocketManager constructor.
53
	 *
54
	 * @param Configuration   $configuration
55
	 * @param LoggerInterface $logger
56
	 * @param EventDispatcher $eventDispatcher
57
	 */
58 4
	public function __construct(
59
		Configuration $configuration,
60
		LoggerInterface $logger,
61
		EventDispatcher $eventDispatcher
62
	) {
63 4
		parent::__construct($configuration, $logger, $eventDispatcher);
64
65
		// Keep track of clients
66 4
		$this->_connectedClients     = new \SplObjectStorage();
67 4
		$this->_authenticatedClients = new \SplObjectStorage();
68 4
	}
69
70
71
	/**
72
	 * @inheritdoc
73
	 */
74 4
	public static function getSubscribedEvents()
75
	{
76
		return [
77 4
			Events::INSTANCE_STATUS_UPDATES => 'onInstanceStatusUpdates',
78 4
		];
79
	}
80
81
82
	/**
83
	 * @param InstanceStatusUpdatesEvent $event
84
	 */
85 1
	public function onInstanceStatusUpdates(InstanceStatusUpdatesEvent $event)
86
	{
87 1
		$this->logger->debug('Broadcasting statuses to all clients');
88 1
		$message = new StatusUpdatesMessage($event->getInstanceStatusCollection());
89
90 1
		foreach ($this->_authenticatedClients as $client)
91
		{
92
			/* @var ConnectionInterface $client */
93 1
			$this->sendMessage($message, $client);
94 1
		}
95 1
	}
96
97
98
	/**
99
	 * @inheritdoc
100
	 */
101 4
	public function onOpen(ConnectionInterface $conn)
102
	{
103 4
		$this->logger->info('Got client connection');
104 4
		$this->_connectedClients->attach($conn);
105 4
	}
106
107
108
	/**
109
	 * @inheritdoc
110
	 */
111
	public function onClose(ConnectionInterface $conn)
112
	{
113
		$this->logger->info('Got client disconnect');
114
115
		$this->_connectedClients->detach($conn);
116
		$this->_authenticatedClients->detach($conn);
117
	}
118
119
120
	/**
121
	 * @inheritdoc
122
	 */
123
	public function onError(ConnectionInterface $conn, \Exception $e)
124
	{
125
		// TODO: Implement onError() method.
126
	}
127
128
129
	/**
130
	 * @inheritdoc
131
	 */
132 3
	public function handleMessage(AbstractMessage $message, ConnectionInterface $sender)
133
	{
134 3
		if ($message->getType() !== AbstractMessage::TYPE_AUTHENTICATION_REQUEST)
135 3
			return false;
136
137
		/* @var AuthenticationRequest $message */
138 3
		$status = AuthenticationResponse::STATUS_FAILURE;
139
140
		// Add the sender to the list of authenticated clients
141 3
		if ($message->getAccessToken() === $this->configuration->getAccessToken())
142 3
		{
143 3
			$status = AuthenticationResponse::STATUS_SUCCESS;
144 3
			$this->_authenticatedClients->attach($sender);
145
146 3
			$this->logger->notice('Client authenticated successfully');
147 3
		}
148
		else
149 1
			$this->logger->warning('Got invalid authentication request from client');
150
151 3
		return new AuthenticationResponse($status);
152
	}
153
154
155
	/**
156
	 * Dispatches incoming client messages to the appropriate handlers
157
	 *
158
	 * @param ConnectionInterface $from
159
	 * @param string              $msg
160
	 */
161 1
	public function onMessage(ConnectionInterface $from, $msg)
162
	{
163
		try
164
		{
165 1
			$message = MessageFactory::factory($msg);
166
167 1
			$this->logger->debug('Got message from client (type: {messageType})', [
168 1
				'messageType' => $message->getType(),
169 1
			]);
170
171
			// Attempt to delegate the message
172
			try
173
			{
174 1
				$response = $this->tryDelegateMessage($message, $from);
175 1
				$this->sendMessage($response, $from);
176
			}
177 1
			catch (RequestFailedException $e)
178
			{
179
				$this->logger->error('The request failed: ' . $e->getMessage());
180
			}
181
			catch (UnhandledMessageException $e)
182
			{
183
				$this->logger->error('Unhandled message (type: {messageType})', [
184
					'messageType' => $message->getType(),
185
				]);
186
			}
187
		}
188 1
		catch (MalformedRequestException $e)
189
		{
190
			$this->logger->error('Got malformed message from client (reason: {reason})', [
191
				'reason' => $e->getMessage(),
192
			]);
193
		}
194
		catch (UnknownRequestException $e)
195
		{
196
			// The server itself sometimes sends out messages that are received here, hence debug
197
			$this->logger->debug('Got unknown message from client (type: {messageType})', [
198
				'messageType' => $e->getType(),
199
			]);
200
		}
201 1
	}
202
203
204
	/**
205
	 * @param AbstractMessage     $message
206
	 * @param ConnectionInterface $target
207
	 */
208 2
	private function sendMessage(AbstractMessage $message, ConnectionInterface $target)
209
	{
210 2
		$target->send(json_encode($message));
211 2
	}
212
213
}
214