Completed
Push — master ( 5c5bf8...f751ec )
by Nikita
05:57
created

Centrifuge::presence()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
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
        ];
68
69
        foreach ($config as $key => $value) {
70
            if (array_key_exists($key, $defaults)) {
71
                $defaults[$key] = $value;
72
            }
73
        }
74
75
        return $defaults;
76
    }
77
78
    /**
79
     * Send message into channel.
80
     *
81
     * @param string $channel
82
     * @param array $data
83
     * @param string $client
84
     * @return mixed
85
     */
86
    public function publish(string $channel, array $data, $client = null)
87
    {
88
        $params = ['channel' => $channel, 'data' => $data];
89
90
        if (! is_null($client)) {
91
            $params['client'] = $client;
92
        }
93
94
        return $this->send('publish', $params);
95
    }
96
97
    /**
98
     * Send message into multiple channel.
99
     *
100
     * @param array $channels
101
     * @param array $data
102
     * @param string $client
103
     * @return mixed
104
     */
105
    public function broadcast(array $channels, array $data, $client = null)
106
    {
107
        $params = ['channels' => $channels, 'data' => $data];
108
109
        if (! is_null($client)) {
110
            $params['client'] = $client;
111
        }
112
113
        return $this->send('broadcast', $params);
114
    }
115
116
    /**
117
     * Get channel presence information (all clients currently subscribed on this channel).
118
     *
119
     * @param string $channel
120
     * @return mixed
121
     */
122
    public function presence(string $channel)
123
    {
124
        return $this->send('presence', ['channel' => $channel]);
125
    }
126
127
    /**
128
     * Get channel history information (list of last messages sent into channel).
129
     *
130
     * @param string $channel
131
     * @return mixed
132
     */
133
    public function history(string $channel)
134
    {
135
        return $this->send('history', ['channel' => $channel]);
136
    }
137
138
    /**
139
     * Unsubscribe user from channel.
140
     *
141
     * @param string $user_id
142
     * @param string $channel
143
     * @return mixed
144
     */
145
    public function unsubscribe($user_id, $channel = null)
146
    {
147
        $params = ['user' => (string) $user_id];
148
149
        if (! is_null($channel)) {
150
            $params['channel'] = $channel;
151
        }
152
153
        return $this->send('unsubscribe', $params);
154
    }
155
156
    /**
157
     * Disconnect user by its ID.
158
     *
159
     * @param string $user_id
160
     * @return mixed
161
     */
162
    public function disconnect($user_id)
163
    {
164
        return $this->send('disconnect', ['user' => (string) $user_id]);
165
    }
166
167
    /**
168
     * Get channels information (list of currently active channels).
169
     *
170
     * @return mixed
171
     */
172
    public function channels()
173
    {
174
        return $this->send('channels');
175
    }
176
177
    /**
178
     * Get stats information about running server nodes.
179
     *
180
     * @return mixed
181
     */
182
    public function stats()
183
    {
184
        return $this->send('stats');
185
    }
186
187
    /**
188
     * Generate token.
189
     *
190
     * @param string $userOrClient
191
     * @param string $timestampOrChannel
192
     * @param string $info
193
     * @return string
194
     */
195
    public function generateToken($userOrClient, $timestampOrChannel, $info = '')
196
    {
197
        $ctx = hash_init('sha256', HASH_HMAC, $this->getSecret());
198
        hash_update($ctx, (string) $userOrClient);
199
        hash_update($ctx, (string) $timestampOrChannel);
200
        hash_update($ctx, (string) $info);
201
202
        return hash_final($ctx);
203
    }
204
205
    /**
206
     * Generate api sign.
207
     *
208
     * @param string $data
209
     * @return string
210
     */
211
    public function generateApiSign($data)
212
    {
213
        $ctx = hash_init('sha256', HASH_HMAC, $this->getSecret());
214
        hash_update($ctx, (string) $data);
215
216
        return hash_final($ctx);
217
    }
218
219
    /**
220
     * Get secret key.
221
     *
222
     * @return string
223
     */
224
    protected function getSecret()
225
    {
226
        return $this->config['secret'];
227
    }
228
229
    /**
230
     * Send message to centrifuge server.
231
     *
232
     * @param  string $method
233
     * @param  array  $params
234
     * @return mixed
235
     */
236
    protected function send(string $method, array $params = [])
237
    {
238
        try {
239
            if ($this->config['redis_api'] === true && ! is_null($this->redisClient) && in_array($method, $this->redisMethods)) {
240
                $result = $this->redisSend($method, $params);
241
            } else {
242
                $result = $this->httpSend($method, $params);
243
            }
244
        } catch (Exception $e) {
245
            $result = [
246
                'method' => $method,
247
                'error'  => $e,
248
                'body'   => $params,
249
            ];
250
        }
251
252
        return $result;
253
    }
254
255
    /**
256
     * Send message to centrifuge server from http client.
257
     *
258
     * @param  string $method
259
     * @param  array  $params
260
     * @return mixed
261
     */
262
    protected function httpSend(string $method, array $params = [])
263
    {
264
        $json = json_encode(['method' => $method, 'params' => $params]);
265
266
        $headers = [
267
            'Content-type' => 'application/json',
268
            'X-API-Sign' => $this->generateApiSign($json),
269
        ];
270
271
        try {
272
            $response = $this->httpClient->post($this->prepareUrl(), [
273
                'headers' => $headers,
274
                'body' => $json,
275
                'http_errors' => false,
276
            ]);
277
278
            $finally = json_decode((string) $response->getBody(), true)[0];
279
        } catch (ClientException $e) {
280
            throw $e;
281
        }
282
283
        return $finally;
284
    }
285
286
    /**
287
     * Prepare URL to send the http request.
288
     *
289
     * @return string
290
     */
291
    protected function prepareUrl()
292
    {
293
        $address = rtrim($this->config['url'], '/');
294
295
        if (substr_compare($address, static::API_PATH, -strlen(static::API_PATH)) !== 0) {
296
            $address .= static::API_PATH;
297
        }
298
        $address .= '/';
299
300
        return $address;
301
    }
302
303
    /**
304
     * Send message to centrifuge server from redis client.
305
     *
306
     * @param  string $method
307
     * @param  array  $params
308
     * @return mixed
309
     */
310
    protected function redisSend(string $method, array $params = [])
311
    {
312
        $json = json_encode(['method' => $method, 'params' => $params]);
313
314
        try {
315
            $this->redisClient->rpush($this->getQueueKey(), $json);
316
        } catch (PredisException $e) {
317
            throw $e;
318
        }
319
320
        return [
321
            'method' => $method,
322
            'error'  => null,
323
            'body'   => null,
324
        ];
325
    }
326
327
    /**
328
     * Get queue key for redis engine.
329
     *
330
     * @return string
331
     */
332
    protected function getQueueKey()
333
    {
334
        $apiKey = $this->config['redis_prefix'].self::REDIS_SUFFIX;
335
        $numShards = (int) $this->config['redis_num_shards'];
336
337
        if ($numShards > 0) {
338
            return sprintf('%s.%d', $apiKey, rand(0, $numShards - 1));
339
        }
340
341
        return $apiKey;
342
    }
343
}
344