Centrifuge   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 347
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 5
dl 0
loc 347
rs 9.76
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A initConfiguration() 0 20 3
A publish() 0 10 2
A broadcast() 0 10 2
A presence() 0 4 1
A history() 0 4 1
A unsubscribe() 0 10 2
A disconnect() 0 4 1
A channels() 0 4 1
A stats() 0 4 1
A generateToken() 0 9 1
A generateApiSign() 0 7 1
A getSecret() 0 4 1
A send() 0 19 5
A httpSend() 0 35 4
A prepareUrl() 0 11 2
A redisSend() 0 16 2
A getQueueKey() 0 11 2
1
<?php
2
3
namespace LaraComponents\Centrifuge;
4
5
use Exception;
6
use Predis\PredisException;
7
use Predis\Client as RedisClient;
8
use GuzzleHttp\Client as HttpClient;
9
use GuzzleHttp\Exception\ClientException;
10
use LaraComponents\Centrifuge\Contracts\Centrifuge as CentrifugeContract;
11
12
class Centrifuge implements CentrifugeContract
13
{
14
    const REDIS_SUFFIX = '.api';
15
16
    const API_PATH = '/api';
17
18
    /**
19
     * @var \GuzzleHttp\Client
20
     */
21
    protected $httpClient;
22
23
    /**
24
     * @var \Predis\Client
25
     */
26
    protected $redisClient;
27
28
    /**
29
     * @var array
30
     */
31
    protected $config;
32
33
    /**
34
     * @var array
35
     */
36
    protected $redisMethods = ['publish', 'broadcast', 'unsubscribe', 'disconnect'];
37
38
    /**
39
     * Create a new Centrifuge instance.
40
     *
41
     * @param array $config
42
     * @param \GuzzleHttp\Client $httpClient
43
     * @param \Predis\Client|null $redisClient
44
     */
45
    public function __construct(array $config, HttpClient $httpClient, RedisClient $redisClient = null)
46
    {
47
        $this->httpClient = $httpClient;
48
        $this->redisClient = $redisClient;
49
50
        $this->config = $this->initConfiguration($config);
51
    }
52
53
    /**
54
     * Init centrifuge configuration.
55
     *
56
     * @param  array $config
57
     * @return array
58
     */
59
    protected function initConfiguration(array $config)
60
    {
61
        $defaults = [
62
            'url'              => 'http://localhost:8000',
63
            'secret'           => null,
64
            'redis_api'        => false,
65
            'redis_prefix'     => 'centrifugo',
66
            'redis_num_shards' => 0,
67
            'ssl_key'          => null,
68
            'verify'           => true,
69
        ];
70
71
        foreach ($config as $key => $value) {
72
            if (array_key_exists($key, $defaults)) {
73
                $defaults[$key] = $value;
74
            }
75
        }
76
77
        return $defaults;
78
    }
79
80
    /**
81
     * Send message into channel.
82
     *
83
     * @param string $channel
84
     * @param array $data
85
     * @param string $client
86
     * @return mixed
87
     */
88
    public function publish($channel, array $data, $client = null)
89
    {
90
        $params = ['channel' => $channel, 'data' => $data];
91
92
        if (! is_null($client)) {
93
            $params['client'] = $client;
94
        }
95
96
        return $this->send('publish', $params);
97
    }
98
99
    /**
100
     * Send message into multiple channel.
101
     *
102
     * @param array $channels
103
     * @param array $data
104
     * @param string $client
105
     * @return mixed
106
     */
107
    public function broadcast(array $channels, array $data, $client = null)
108
    {
109
        $params = ['channels' => $channels, 'data' => $data];
110
111
        if (! is_null($client)) {
112
            $params['client'] = $client;
113
        }
114
115
        return $this->send('broadcast', $params);
116
    }
117
118
    /**
119
     * Get channel presence information (all clients currently subscribed on this channel).
120
     *
121
     * @param string $channel
122
     * @return mixed
123
     */
124
    public function presence($channel)
125
    {
126
        return $this->send('presence', ['channel' => $channel]);
127
    }
128
129
    /**
130
     * Get channel history information (list of last messages sent into channel).
131
     *
132
     * @param string $channel
133
     * @return mixed
134
     */
135
    public function history($channel)
136
    {
137
        return $this->send('history', ['channel' => $channel]);
138
    }
139
140
    /**
141
     * Unsubscribe user from channel.
142
     *
143
     * @param string $user_id
144
     * @param string $channel
145
     * @return mixed
146
     */
147
    public function unsubscribe($user_id, $channel = null)
148
    {
149
        $params = ['user' => (string) $user_id];
150
151
        if (! is_null($channel)) {
152
            $params['channel'] = $channel;
153
        }
154
155
        return $this->send('unsubscribe', $params);
156
    }
157
158
    /**
159
     * Disconnect user by its ID.
160
     *
161
     * @param string $user_id
162
     * @return mixed
163
     */
164
    public function disconnect($user_id)
165
    {
166
        return $this->send('disconnect', ['user' => (string) $user_id]);
167
    }
168
169
    /**
170
     * Get channels information (list of currently active channels).
171
     *
172
     * @return mixed
173
     */
174
    public function channels()
175
    {
176
        return $this->send('channels');
177
    }
178
179
    /**
180
     * Get stats information about running server nodes.
181
     *
182
     * @return mixed
183
     */
184
    public function stats()
185
    {
186
        return $this->send('stats');
187
    }
188
189
    /**
190
     * Generate token.
191
     *
192
     * @param string $userOrClient
193
     * @param string $timestampOrChannel
194
     * @param string $info
195
     * @return string
196
     */
197
    public function generateToken($userOrClient, $timestampOrChannel, $info = '')
198
    {
199
        $ctx = hash_init('sha256', HASH_HMAC, $this->getSecret());
200
        hash_update($ctx, (string) $userOrClient);
201
        hash_update($ctx, (string) $timestampOrChannel);
202
        hash_update($ctx, (string) $info);
203
204
        return hash_final($ctx);
205
    }
206
207
    /**
208
     * Generate api sign.
209
     *
210
     * @param string $data
211
     * @return string
212
     */
213
    public function generateApiSign($data)
214
    {
215
        $ctx = hash_init('sha256', HASH_HMAC, $this->getSecret());
216
        hash_update($ctx, (string) $data);
217
218
        return hash_final($ctx);
219
    }
220
221
    /**
222
     * Get secret key.
223
     *
224
     * @return string
225
     */
226
    protected function getSecret()
227
    {
228
        return $this->config['secret'];
229
    }
230
231
    /**
232
     * Send message to centrifuge server.
233
     *
234
     * @param  string $method
235
     * @param  array $params
236
     * @return mixed
237
     */
238
    protected function send($method, array $params = [])
239
    {
240
        try {
241
            if ($this->config['redis_api'] === true && ! is_null($this->redisClient) && in_array($method,
242
                    $this->redisMethods)) {
243
                $result = $this->redisSend($method, $params);
244
            } else {
245
                $result = $this->httpSend($method, $params);
246
            }
247
        } catch (Exception $e) {
248
            $result = [
249
                'method' => $method,
250
                'error'  => $e->getMessage(),
251
                'body'   => $params,
252
            ];
253
        }
254
255
        return $result;
256
    }
257
258
    /**
259
     * Send message to centrifuge server from http client.
260
     *
261
     * @param  string $method
262
     * @param  array $params
263
     * @return mixed
264
     */
265
    protected function httpSend($method, array $params = [])
266
    {
267
        $json = json_encode(['method' => $method, 'params' => $params]);
268
269
        $headers = [
270
            'Content-type' => 'application/json',
271
            'X-API-Sign'   => $this->generateApiSign($json),
272
        ];
273
274
        try {
275
            $url = parse_url($this->prepareUrl());
276
277
            $config = collect([
278
                'headers'     => $headers,
279
                'body'        => $json,
280
                'http_errors' => false,
281
            ]);
282
283
            if ($url['scheme'] == 'https') {
284
                $config->put('verify', collect($this->config)->get('verify', false));
285
286
                if (collect($this->config)->get('ssl_key')) {
287
                    $config->put('ssl_key', collect($this->config)->get('ssl_key'));
288
                }
289
            }
290
291
            $response = $this->httpClient->post($this->prepareUrl(), $config->toArray());
292
293
            $finally = json_decode((string) $response->getBody(), true)[0];
294
        } catch (ClientException $e) {
295
            throw $e;
296
        }
297
298
        return $finally;
299
    }
300
301
    /**
302
     * Prepare URL to send the http request.
303
     *
304
     * @return string
305
     */
306
    protected function prepareUrl()
307
    {
308
        $address = rtrim($this->config['url'], '/');
309
310
        if (substr_compare($address, static::API_PATH, -strlen(static::API_PATH)) !== 0) {
311
            $address .= static::API_PATH;
312
        }
313
        $address .= '/';
314
315
        return $address;
316
    }
317
318
    /**
319
     * Send message to centrifuge server from redis client.
320
     * @param $method
321
     * @param array $params
322
     * @return array
323
     * @throws PredisException
324
     */
325
    protected function redisSend($method, array $params = [])
326
    {
327
        $json = json_encode(['method' => $method, 'params' => $params]);
328
329
        try {
330
            $this->redisClient->rpush($this->getQueueKey(), $json);
331
        } catch (PredisException $e) {
332
            throw $e;
333
        }
334
335
        return [
336
            'method' => $method,
337
            'error'  => null,
338
            'body'   => null,
339
        ];
340
    }
341
342
    /**
343
     * Get queue key for redis engine.
344
     *
345
     * @return string
346
     */
347
    protected function getQueueKey()
348
    {
349
        $apiKey = $this->config['redis_prefix'].self::REDIS_SUFFIX;
350
        $numShards = (int) $this->config['redis_num_shards'];
351
352
        if ($numShards > 0) {
353
            return sprintf('%s.%d', $apiKey, rand(0, $numShards - 1));
354
        }
355
356
        return $apiKey;
357
    }
358
}
359