Centrifugo::unsubscribe()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Opekunov\Centrifugo;
6
7
use Carbon\Carbon;
8
use GuzzleHttp\Client as HttpClient;
9
use GuzzleHttp\Exception\ClientException;
10
use Opekunov\Centrifugo\Contracts\CentrifugoInterface;
11
12
class Centrifugo implements CentrifugoInterface
13
{
14
    const API_PATH = '/api';
15
16
    /**
17
     * @var \GuzzleHttp\Client
18
     */
19
    protected $httpClient;
20
21
    /**
22
     * @var array
23
     */
24
    protected $config;
25
26
    /**
27
     * Create a new Centrifugo instance.
28
     *
29
     * @param array              $config
30
     * @param \GuzzleHttp\Client $httpClient
31
     */
32
    public function __construct(array $config = null, HttpClient $httpClient = null)
33
    {
34
        $this->httpClient = $httpClient ?? new HttpClient();
35
        if (!$config) {
36
            $this->config = app()->make('config')->get('broadcasting.connections.centrifugo');
37
        } else {
38
            $this->config = $this->initConfiguration($config ?? []) ??
39
                app()->make('config')->get('broadcasting.connections.centrifugo');
40
        }
41
    }
42
43
    /**
44
     * Init centrifugo configuration.
45
     *
46
     * @param array $config
47
     *
48
     * @return array
49
     */
50
    protected function initConfiguration(array $config)
51
    {
52
        $defaults = [
53
            'url'            => 'http://localhost:8000',
54
            'secret'         => null,
55
            'apikey'         => null,
56
            'ssl_key'        => null,
57
            'verify'         => true,
58
            'show_node_info' => false,
59
            'timeout'        => 10,
60
        ];
61
62
        foreach ($config as $key => $value) {
63
            if (array_key_exists($key, $defaults)) {
64
                $defaults[$key] = $value;
65
            }
66
        }
67
68
        return $defaults;
69
    }
70
71
    /**
72
     * Send message into channel.
73
     *
74
     * @param string $channel
75
     * @param array  $data
76
     *
77
     * @return mixed
78
     */
79
    public function publish(string $channel, array $data)
80
    {
81
        return $this->send('publish', [
82
            'channel' => $channel,
83
            'data'    => $data,
84
        ]);
85
    }
86
87
    /**
88
     * Send message into multiple channel.
89
     *
90
     * @param array $channels
91
     * @param array $data
92
     *
93
     * @return mixed
94
     */
95
    public function broadcast(array $channels, array $data)
96
    {
97
        $params = ['channels' => $channels, 'data' => $data];
98
99
        return $this->send('broadcast', $params);
100
    }
101
102
    /**
103
     * Get channel presence information (all clients currently subscribed on this channel).
104
     *
105
     * @param string $channel
106
     *
107
     * @return mixed
108
     */
109
    public function presence(string $channel)
110
    {
111
        return $this->send('presence', ['channel' => $channel]);
112
    }
113
114
    /**
115
     * Get channel presence information in short form.
116
     *
117
     * @param string $channel
118
     *
119
     * @return mixed
120
     */
121
    public function presenceStats(string $channel)
122
    {
123
        return $this->send('presence_stats', ['channel' => $channel]);
124
    }
125
126
    /**
127
     * Get channel history information (list of last messages sent into channel).
128
     *
129
     * @param string $channel
130
     *
131
     * @return mixed
132
     */
133
    public function history(string $channel)
134
    {
135
        return $this->send('history', ['channel' => $channel]);
136
    }
137
138
    /**
139
     * Remove channel history information.
140
     *
141
     * @param string $channel
142
     *
143
     * @return mixed
144
     */
145
    public function historyRemove(string $channel)
146
    {
147
        return $this->send('history_remove', [
148
            'channel' => $channel,
149
        ]);
150
    }
151
152
    /**
153
     * Unsubscribe user from channel.
154
     *
155
     * @param string $channel
156
     * @param string $user
157
     *
158
     * @return mixed
159
     */
160
    public function unsubscribe(string $channel, string $user)
161
    {
162
        return $this->send('unsubscribe', [
163
            'channel' => $channel,
164
            'user'    => $user,
165
        ]);
166
    }
167
168
    /**
169
     * Disconnect user by its ID.
170
     *
171
     * @param string $user_id
172
     *
173
     * @return mixed
174
     */
175
    public function disconnect(string $user_id)
176
    {
177
        return $this->send('disconnect', ['user' => (string) $user_id]);
178
    }
179
180
    /**
181
     * Get channels information (list of currently active channels).
182
     *
183
     * @return mixed
184
     */
185
    public function channels()
186
    {
187
        return $this->send('channels');
188
    }
189
190
    /**
191
     * Get stats information about running server nodes.
192
     *
193
     * @return mixed
194
     */
195
    public function info()
196
    {
197
        return $this->send('info');
198
    }
199
200
    /**
201
     * Generate connection token.
202
     *
203
     * @param string     $userId
204
     * @param int|Carbon $exp
205
     * @param array      $info
206
     *
207
     * @return string
208
     */
209
    public function generateConnectionToken(string $userId = '', $exp = 0, array $info = []): string
210
    {
211
        if (gettype($exp) !== 'integer') {
212
            $exp = $exp->unix();
213
        }
214
        $header = ['typ' => 'JWT', 'alg' => 'HS256'];
215
        $payload = ['sub' => $userId];
216
        if (!empty($info)) {
217
            $payload['info'] = $info;
218
        }
219
        if ($exp) {
220
            $payload['exp'] = $exp;
221
        }
222
        $segments = [];
223
        $segments[] = $this->urlsafeB64Encode(json_encode($header));
224
        $segments[] = $this->urlsafeB64Encode(json_encode($payload));
225
        $signing_input = implode('.', $segments);
226
        $signature = $this->sign($signing_input, $this->getSecret());
227
        $segments[] = $this->urlsafeB64Encode($signature);
228
229
        return implode('.', $segments);
230
    }
231
232
    /**
233
     * Generate private channel token.
234
     *
235
     * @param string     $client
236
     * @param string     $channel
237
     * @param int|Carbon $exp
238
     * @param array      $info
239
     *
240
     * @return string
241
     */
242
    public function generatePrivateChannelToken(string $client, string $channel, $exp = 0, array $info = []): string
243
    {
244
        if (gettype($exp) !== 'integer') {
245
            $exp = $exp->unix();
246
        }
247
        $header = ['typ' => 'JWT', 'alg' => 'HS256'];
248
        $payload = ['channel' => $channel, 'client' => $client];
249
        if (!empty($info)) {
250
            $payload['info'] = $info;
251
        }
252
        if ($exp) {
253
            $payload['exp'] = $exp;
254
        }
255
        $segments = [];
256
        $segments[] = $this->urlsafeB64Encode(json_encode($header));
257
        $segments[] = $this->urlsafeB64Encode(json_encode($payload));
258
        $signing_input = implode('.', $segments);
259
        $signature = $this->sign($signing_input, $this->getSecret());
260
        $segments[] = $this->urlsafeB64Encode($signature);
261
262
        return implode('.', $segments);
263
    }
264
265
    /**
266
     * Get secret key.
267
     *
268
     * @return string
269
     */
270
    protected function getSecret()
271
    {
272
        return $this->config['secret'];
273
    }
274
275
    /**
276
     * Send message to centrifugo server.
277
     *
278
     * @param string $method
279
     * @param array  $params
280
     *
281
     * @return mixed
282
     */
283
    protected function send($method, array $params = [])
284
    {
285
        $json = json_encode(['method' => $method, 'params' => $params]);
286
287
        $headers = [
288
            'Content-type'  => 'application/json',
289
            'Authorization' => 'apikey '.$this->config['apikey'],
290
        ];
291
292
        try {
293
            $url = parse_url($this->prepareUrl());
294
295
            $config = collect([
296
                'headers'     => $headers,
297
                'body'        => $json,
298
                'http_errors' => true,
299
                'timeout'     => $this->config['timeout'],
300
            ]);
301
302
            if ($url['scheme'] == 'https') {
303
                $config->put('verify', collect($this->config)->get('verify', false));
304
305
                if (collect($this->config)->get('ssl_key')) {
306
                    $config->put('ssl_key', collect($this->config)->get('ssl_key'));
307
                }
308
            }
309
310
            $response = $this->httpClient->post($this->prepareUrl(), $config->toArray());
311
312
            $result = json_decode((string) $response->getBody(), true);
313
        } catch (ClientException $e) {
314
            $result = [
315
                'method' => $method,
316
                'error'  => $e->getMessage(),
317
                'body'   => $params,
318
            ];
319
        }
320
321
        return $result;
322
    }
323
324
    /**
325
     * Prepare URL to send the http request.
326
     *
327
     * @return string
328
     */
329
    protected function prepareUrl()
330
    {
331
        $address = rtrim($this->config['url'], '/');
332
333
        if (substr_compare($address, static::API_PATH, -strlen(static::API_PATH)) !== 0) {
334
            $address .= static::API_PATH;
335
        }
336
        //$address .= '/';
337
338
        return $address;
339
    }
340
341
    /**
342
     * Safely encode string in base64.
343
     *
344
     * @param string $input
345
     *
346
     * @return string
347
     */
348
    private function urlsafeB64Encode($input)
349
    {
350
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
351
    }
352
353
    /**
354
     * Sign message with secret key.
355
     *
356
     * @param string $msg
357
     * @param string $key
358
     *
359
     * @return string
360
     */
361
    private function sign($msg, $key)
362
    {
363
        return hash_hmac('sha256', $msg, $key, true);
364
    }
365
366
    /**
367
     * Can show Node info when return auth token.
368
     *
369
     * @return bool
370
     */
371
    public function showNodeInfo(): bool
372
    {
373
        return (bool) $this->config['show_node_info'];
374
    }
375
}
376