Passed
Push — master ( c8e4bc...17399e )
by Jacobo
04:15
created

Session   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 425
Duplicated Lines 0 %

Importance

Changes 15
Bugs 0 Features 1
Metric Value
eloc 154
c 15
b 0
f 1
dl 0
loc 425
rs 4.5599
wmc 58

20 Methods

Rating   Name   Duplication   Size   Complexity  
A forceDisconnect() 0 19 4
A fetch() 0 18 3
A toJson() 0 4 1
A setIsBeingRecorded() 0 4 1
A __construct() 0 5 2
A getSessionId() 0 18 5
A fromArray() 0 27 6
A publish() 0 14 4
A isBeingRecorded() 0 3 1
A getActiveConnections() 0 3 1
A forceUnpublish() 0 24 6
A setLastRecordingId() 0 4 1
A close() 0 11 3
A leaveSession() 0 14 5
A __toString() 0 3 1
A generateToken() 0 22 6
A fromJson() 0 3 1
A toArray() 0 13 5
A getLastRecordingId() 0 3 1
A jsonSerialize() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SquareetLabs\LaravelOpenVidu;
4
5
use Exception;
6
use GuzzleHttp\Client;
7
use GuzzleHttp\RequestOptions;
8
use Illuminate\Support\Facades\Cache;
9
use JsonSerializable;
10
use SquareetLabs\LaravelOpenVidu\Builders\ConnectionBuilder;
11
use SquareetLabs\LaravelOpenVidu\Builders\PublisherBuilder;
12
use SquareetLabs\LaravelOpenVidu\Builders\SessionPropertiesBuilder;
13
use SquareetLabs\LaravelOpenVidu\Builders\SubscriberBuilder;
14
use SquareetLabs\LaravelOpenVidu\Enums\MediaMode;
15
use SquareetLabs\LaravelOpenVidu\Enums\OpenViduRole;
16
use SquareetLabs\LaravelOpenVidu\Enums\OutputMode;
17
use SquareetLabs\LaravelOpenVidu\Enums\RecordingLayout;
18
use SquareetLabs\LaravelOpenVidu\Enums\RecordingMode;
19
use SquareetLabs\LaravelOpenVidu\Enums\Uri;
20
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduConnectionNotFoundException;
21
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduException;
22
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduProblemWithBodyParameterException;
23
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduSessionNotFoundException;
24
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduStreamCantCloseException;
25
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduTokenCantCreateException;
26
27
/**
28
 * Class Session
29
 * @package SquareetLabs\LaravelOpenVidu
30
 */
31
class Session implements JsonSerializable
32
{
33
    /** @var  Client */
34
    private $client;
35
36
    /** @var  string */
37
    private $sessionId;
38
39
    /** @var SessionProperties */
40
    private $properties;
41
42
    /** @var bool */
43
    private $recording;
44
45
    /** @var string */
46
    private $lastRecordingId;
47
48
    /** @var array */
49
    private $activeConnections = [];
50
51
    /** @var int */
52
    private $createdAt;
53
54
    /**
55
     * Session constructor.
56
     * @param  Client  $client
57
     * @param  SessionProperties|null  $properties
58
     * @throws OpenViduException
59
     */
60
    public function __construct(Client $client, ?SessionProperties $properties = null)
61
    {
62
        $this->client = $client;
63
        $this->properties = $properties ? $properties : new SessionProperties(MediaMode::ROUTED, RecordingMode::MANUAL, OutputMode::COMPOSED, RecordingLayout::BEST_FIT);
64
        $this->sessionId = $this->getSessionId();
65
    }
66
67
    /**
68
     * @return string
69
     * @throws OpenViduException
70
     */
71
    public function getSessionId()
72
    {
73
        if (empty($this->sessionId)) {
74
            $response = $this->client->post(Uri::SESSION_URI, [
75
                RequestOptions::JSON => $this->properties->toArray()
76
            ]);
77
            switch ($response->getStatusCode()) {
78
                case 200:
79
                    return json_decode($response->getBody()->getContents())->id;
80
                case 400:
81
                    throw  new OpenViduProblemWithBodyParameterException();
82
                case 409:
83
                    return $this->properties->getCustomSessionId();
84
                default:
85
                    throw new OpenViduException("Invalid response status code ".$response->getStatusCode(), $response->getStatusCode());
86
            }
87
        } else {
88
            return $this->sessionId;
89
        }
90
    }
91
92
    /**
93
     ** Gets a new token associated to Session object with default values for
94
     * {@see TokenOptions}. This always translates into a
95
     * new request to OpenVidu Server
96
     *
97
     * @param  TokenOptions|null  $tokenOptions
98
     * @return string The generated token
99
     * @throws OpenViduException
100
     */
101
    public function generateToken(?TokenOptions $tokenOptions = null)
102
    {
103
        $this->getSessionId();
104
        try {
105
            if (!$tokenOptions) {
106
                $tokenOptions = new TokenOptions(OpenViduRole::PUBLISHER);
107
            }
108
            $response = $this->client->post(Uri::TOKEN_URI, [
109
                RequestOptions::JSON => array_merge($tokenOptions->toArray(), ['session' => $this->sessionId])
110
            ]);
111
            switch ($response->getStatusCode()) {
112
                case 200:
113
                    return json_decode($response->getBody()->getContents());
114
                case 400:
115
                    throw new OpenViduProblemWithBodyParameterException();
116
                case 404:
117
                    throw new OpenViduSessionNotFoundException();
118
                default:
119
                    throw new OpenViduException("Invalid response status code ".$response->getStatusCode(), $response->getStatusCode());
120
            }
121
        } catch (Exception $e) {
122
            throw new OpenViduTokenCantCreateException($e->getMessage(), $e);
123
        }
124
    }
125
126
    /**
127
     * Gracefully closes the Session: unpublishes all streams and evicts every
128
     * participant
129
     * @throws OpenViduException
130
     */
131
    public function close()
132
    {
133
        $response = $this->client->delete(Uri::SESSION_URI.'/'.$this->sessionId);
134
        switch ($response->getStatusCode()) {
135
            case 204:
136
                Cache::store('openvidu')->forget($this->sessionId);
137
                return true;
138
            case 404:
139
                throw new OpenViduSessionNotFoundException();
140
            default:
141
                throw new OpenViduException("Invalid response status code ".$response->getStatusCode(), $response->getStatusCode());
142
        }
143
    }
144
145
    /**
146
     * Updates every property of the Session with the current status it has in
147
     * OpenVidu Server. This is especially useful for getting the list of active
148
     * connections to the Session
149
     * ({@see getActiveConnections()}) and use
150
     * those values to call
151
     * {@see forceDisconnect(Connection)} or
152
     * {@link forceUnpublish(Publisher)}. <br>
153
     *
154
     * To update every Session object owned by OpenVidu object, call
155
     * {@see fetch()}
156
     *
157
     * @return bool true if the Session status has changed with respect to the server,
158
     * false if not. This applies to any property or sub-property of the object
159
     */
160
161
    public function fetch()
162
    {
163
        $response = $this->client->get(Uri::SESSION_URI.'/'.$this->sessionId, [
164
            'headers' => [
165
                'Content-Type' => 'application/x-www-form-urlencoded',
166
                'Accept' => 'application/json',
167
            ]
168
        ]);
169
        if ($response->getStatusCode() === 200) {
170
            $beforeJSON = $this->toJson();
171
            $this->fromJson($response->getBody()->getContents());
172
            $afterJSON = $this->toJson();
173
            if ($beforeJSON !== $afterJSON) {
174
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
175
                return true;
176
            }
177
        }
178
        return false;
179
    }
180
181
    /**
182
     * Convert the model instance to JSON.
183
     *
184
     * @param  int  $options
185
     * @return string
186
     *
187
     */
188
    public function toJson($options = 0): string
189
    {
190
        $json = json_encode($this->jsonSerialize(), $options);
191
        return $json;
192
    }
193
194
    /**
195
     * Specify data which should be serialized to JSON
196
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
197
     * @return mixed data which can be serialized by <b>json_encode</b>,
198
     * which is a value of any type other than a resource.
199
     * @since 5.4.0
200
     */
201
    public function jsonSerialize()
202
    {
203
        return $this->toArray();
204
    }
205
206
    /**
207
     * Convert the model instance to an array.
208
     *
209
     * @return array
210
     */
211
    public function toArray(): array
212
    {
213
        $array = ['sessionId' => $this->sessionId, 'properties' => $this->properties->toArray(), 'recording' => $this->recording, 'createdAt' => $this->createdAt];
214
        foreach ($this->activeConnections as $connection) {
215
            $array['activeConnections'][] = $connection->toArray();
216
        }
217
218
        foreach ($array as $key => $value) {
219
            if (is_null($value) || $value == '') {
220
                unset($array[$key]);
221
            }
222
        }
223
        return $array;
224
    }
225
226
    /**
227
     * @param  string  $json
228
     * @return Session
229
     */
230
    public function fromJson(string $json): Session
231
    {
232
        return $this->fromArray(json_decode($json, true));
233
    }
234
235
    /**
236
     * @param  array  $sessionArray
237
     * @return Session
238
     */
239
    public function fromArray(array $sessionArray): Session
240
    {
241
        $this->sessionId = $sessionArray['sessionId'];
242
        $this->createdAt = $sessionArray['createdAt'] ?? null;
243
        $this->recording = $sessionArray['recording'] ?? null;
244
245
        if (array_key_exists('properties', $sessionArray)) {
246
            $this->properties = SessionPropertiesBuilder::build($sessionArray['properties']);
247
        }
248
249
        $this->activeConnections = [];
250
        if (array_key_exists('connections', $sessionArray)) {
251
            foreach ($sessionArray['connections']['content'] as $connection) {
252
                $publishers = [];
253
                $ensure = $connection['content'] ?? $connection;
254
                foreach ($ensure['publishers'] as $publisher) {
255
                    $publishers[] = PublisherBuilder::build($publisher);
256
                }
257
                $subscribers = [];
258
259
                foreach ($ensure['subscribers'] as $subscriber) {
260
                    $subscribers[] = SubscriberBuilder::build($subscriber);
261
                }
262
                $this->activeConnections[] = ConnectionBuilder::build($ensure, $publishers, $subscribers);
263
            }
264
        }
265
        return $this;
266
    }
267
268
    /**
269
     * Forces the user with Connection `connectionId` to leave the session. OpenVidu Browser will trigger the proper events on the client-side
270
     * (`streamDestroyed`, `connectionDestroyed`, `sessionDisconnected`) with reason set to `"forceDisconnectByServer"`
271
     *
272
     *
273
     * @param  string  $connectionId
274
     * @throws OpenViduException
275
     */
276
    public function forceDisconnect(string $connectionId)
277
    {
278
        $response = $this->client->delete(Uri::SESSION_URI.'/'.$this->sessionId.'/connection/'.$connectionId, [
279
            'headers' => [
280
                'Content-Type' => 'application/x-www-form-urlencoded',
281
                'Accept' => 'application/json',
282
            ]
283
        ]);
284
        switch ($response->getStatusCode()) {
285
            case 204:
286
                $this->leaveSession($connectionId);
287
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
288
                break;
289
            case 400:
290
                throw new OpenViduSessionNotFoundException();
291
            case 404:
292
                throw new OpenViduConnectionNotFoundException();
293
            default:
294
                throw new OpenViduException("Invalid response status code ".$response->getStatusCode(), $response->getStatusCode());
295
        }
296
    }
297
298
    /**
299
     * Get `connection` parameter from activeConnections array {@see Connection::getConnectionId()} for getting each `connectionId` property).
300
     * Remember to call {@see fetch()} before to fetch the current actual properties of the Session from OpenVidu Server
301
     * @param  string  $connectionId
302
     */
303
304
    private function leaveSession(string $connectionId)
305
    {
306
        $connectionClosed = null;
307
        $this->activeConnections = array_filter($this->activeConnections, function (Connection $connection) use (&$connectionClosed, $connectionId) {
308
            if ($connection->getConnectionId() !== $connectionId) {
309
                return true;
310
            }
311
            $connectionClosed = $connection;
312
            return false;
313
        });
314
        if ($connectionClosed != null) {
315
            foreach ($connectionClosed->getPublishers() as $publisher) {
316
                foreach ($this->activeConnections as $con) {
317
                    $con->unsubscribe($publisher->getStreamId());
318
                }
319
            }
320
        }
321
    }
322
323
324
    /**
325
     * Allows publish a Stream (For now can only be IPCAM).
326
     * Remember to call
327
     * {@see fetch()} before to fetch the current
328
     * actual properties of the Session from OpenVidu Server
329
     *
330
     * @param  IPCameraOptions  $IPCameraOptions
331
     * @return Connection
332
     * @throws OpenViduException
333
     * @throws OpenViduSessionNotFoundException
334
     */
335
    public function publish(IPCameraOptions $IPCameraOptions)
336
    {
337
        $response = $this->client->post(Uri::SESSION_URI.'/'.$this->sessionId.'/connection', [
338
            RequestOptions::JSON => array_merge($IPCameraOptions->toArray())
339
        ]);
340
        switch ($response->getStatusCode()) {
341
            case 200:
342
                return json_decode($response->getBody()->getContents());
343
            case 400:
344
                throw new OpenViduProblemWithBodyParameterException();
345
            case 404:
346
                throw new OpenViduSessionNotFoundException();
347
            default:
348
                throw new OpenViduException("Unexpected error when publishing the IP camera stream into the session.");
349
        }
350
    }
351
352
    /**
353
     * Forces some user to unpublish a Stream. OpenVidu Browser will trigger the
354
     * proper events on the client-side (<code>streamDestroyed</code>) with reason
355
     * set to "forceUnpublishByServer". <br>
356
     *
357
     * You can get <code>streamId</code> parameter with
358
     * {@see Session::getActiveConnections()} and then for
359
     * each Connection you can call
360
     * {@see  Connection::getPublishers()}. Finally
361
     * {@see Publisher::getStreamId()}) will give you the
362
     * <code>streamId</code>. Remember to call
363
     * {@see fetch()} before to fetch the current
364
     * actual properties of the Session from OpenVidu Server
365
     *
366
     * @param  string  $streamId
367
     * @return void
368
     * @throws OpenViduConnectionNotFoundException
369
     * @throws OpenViduException
370
     * @throws OpenViduSessionNotFoundException
371
     */
372
    public function forceUnpublish(string $streamId)
373
    {
374
        $response = $this->client->delete(Uri::SESSION_URI.'/'.$this->sessionId.'/stream/'.$streamId, [
375
            'headers' => [
376
                'Content-Type' => 'application/x-www-form-urlencoded',
377
                'Accept' => 'application/json',
378
            ]
379
        ]);
380
        switch ($response->getStatusCode()) {
381
            case 204:
382
                foreach ($this->activeConnections as $connection) {
383
                    $connection->unpublish($streamId);
384
                    $connection->unsubscribe($streamId);
385
                }
386
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
387
                break;
388
            case 400:
389
                throw new OpenViduSessionNotFoundException();
390
            case 404:
391
                throw new OpenViduConnectionNotFoundException();
392
            case 405:
393
                throw new OpenViduStreamCantCloseException("You cannot directly delete the stream of an IPCAM participant");
394
            default:
395
                throw new OpenViduException("Invalid response status code ".$response->getStatusCode(), $response->getStatusCode());
396
        }
397
    }
398
399
    /**
400
     * Returns the list of active connections to the session. <strong>This value
401
     * will remain unchanged since the last time method
402
     * {@see fetch()} was called</strong>.
403
     * Exceptions to this rule are:
404
     * <ul>
405
     * <li>Calling {@see Session::forceUnpublish(string)}
406
     * updates each affected Connection status</li>
407
     * <li>Calling {@see Session::forceDisconnect(string)}
408
     * updates each affected Connection status</li>
409
     * </ul>
410
     * <br>
411
     * To get the list of active connections with their current actual value, you
412
     * must call first {@see Session::fetch()} and then
413
     * {@see Session::getActiveConnections()}
414
     */
415
    public function getActiveConnections(): array
416
    {
417
        return $this->activeConnections;
418
    }
419
420
    /**
421
     * Returns whether the session is being recorded or not
422
     */
423
    public function isBeingRecorded(): bool
424
    {
425
        return !!$this->recording;
426
    }
427
428
    /**
429
     * Set value
430
     * @param  bool  $recording
431
     */
432
    public function setIsBeingRecorded(bool $recording)
433
    {
434
        $this->recording = $recording;
435
        Cache::store('openvidu')->update($this->sessionId, $this->toJson());
436
    }
437
438
    public function getLastRecordingId()
439
    {
440
        return $this->lastRecordingId;
441
    }
442
443
    public function setLastRecordingId(string $lastRecordingId)
444
    {
445
        $this->lastRecordingId = $lastRecordingId;
446
        Cache::store('openvidu')->update($this->sessionId, $this->toJson());
447
    }
448
449
    /**
450
     * @return string
451
     * @throws OpenViduException
452
     */
453
    public function __toString(): string
454
    {
455
        return $this->getSessionId();
456
    }
457
}
458