Passed
Push — master ( 77f1b4...a6165e )
by Jacobo
03:07
created

Session::setIsBeingRecorded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 1
b 0
f 0
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\Enums\MediaMode;
14
use SquareetLabs\LaravelOpenVidu\Enums\OpenViduRole;
15
use SquareetLabs\LaravelOpenVidu\Enums\OutputMode;
16
use SquareetLabs\LaravelOpenVidu\Enums\RecordingLayout;
17
use SquareetLabs\LaravelOpenVidu\Enums\RecordingMode;
18
use SquareetLabs\LaravelOpenVidu\Enums\Uri;
19
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduConnectionNotFoundException;
20
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduException;
21
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduSessionNotFoundException;
22
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduTokenCantCreateException;
23
24
/**
25
 * Class Session
26
 * @package SquareetLabs\LaravelOpenVidu
27
 */
28
class Session implements JsonSerializable
29
{
30
    /** @var  Client */
31
    private $client;
32
33
    /** @var  string */
34
    private $sessionId;
35
36
    /** @var SessionProperties */
37
    private $properties;
38
39
    /** @var bool */
40
    private $recording;
41
42
    /** @var array */
43
    private $activeConnections = [];
44
45
    /** @var int */
46
    private $createdAt;
47
48
    /**
49
     * Session constructor.
50
     * @param Client $client
51
     * @param SessionProperties|null $properties
52
     * @throws OpenViduException
53
     */
54
    public function __construct(Client $client, ?SessionProperties $properties = null)
55
    {
56
        $this->client = $client;
57
        $this->properties = $properties ? $properties : $this->getDefaultSessionProperties();
58
        $this->sessionId = $this->getSessionId();
59
    }
60
61
    /**
62
     ** Gets a new token associated to Session object with default values for
63
     * {@see TokenOptions}. This always translates into a
64
     * new request to OpenVidu Server
65
     *
66
     * @param TokenOptions|null $tokenOptions
67
     * @return string The generated token
68
     * @throws OpenViduException
69
     */
70
    public function generateToken(?TokenOptions $tokenOptions = null)
71
    {
72
        if (!$this->hasSessionId()) {
73
            $this->getSessionId();
74
        }
75
        try {
76
            if (!$tokenOptions) {
77
                $tokenOptions = $this->getDefaultTokenOptions();
78
            }
79
            $response = $this->client->post(Uri::TOKEN_URI, [
80
                RequestOptions::JSON => array_merge($tokenOptions->toArray(), ['session' => $this->sessionId])
81
            ]);
82
            return json_decode($response->getBody()->getContents());
83
        } catch (Exception $e) {
84
            throw new OpenViduTokenCantCreateException($e->getMessage(), $e);
85
        }
86
    }
87
88
    /**
89
     * @return TokenOptions
90
     */
91
    private function getDefaultTokenOptions(): TokenOptions
92
    {
93
        return new TokenOptions(OpenViduRole::PUBLISHER);
94
    }
95
96
97
    /**
98
     * @return SessionProperties
99
     */
100
    private function getDefaultSessionProperties(): SessionProperties
101
    {
102
        return new SessionProperties(MediaMode::ROUTED, RecordingMode::MANUAL, OutputMode::COMPOSED, RecordingLayout::BEST_FIT);
103
    }
104
105
    /**
106
     * @return string
107
     * @throws OpenViduException
108
     */
109
    public function getSessionId()
110
    {
111
        if (!$this->hasSessionId()) {
112
            $response = $this->client->post(Uri::SESSION_URI, [
113
                RequestOptions::JSON => $this->properties->toArray()
114
            ]);
115
            if ($response->getStatusCode() == 200) {
116
                return json_decode($response->getBody()->getContents())->id;
117
            } else if ($response->getStatusCode() == 409) {
118
                return $this->properties->getCustomSessionId();
119
            } else {
120
                $result = json_decode($response->getBody()->getContents());
121
                if ($result && property_exists($result, 'message')) {
122
                    throw new OpenViduException($result->message, $response->getStatusCode());
123
                }
124
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
125
            }
126
        } else {
127
            return $this->sessionId;
128
        }
129
    }
130
131
132
    /**
133
     * Gracefully closes the Session: unpublishes all streams and evicts every
134
     * participant
135
     * @throws OpenViduException
136
     */
137
    public function close()
138
    {
139
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId);
140
        switch ($response->getStatusCode()) {
141
            case 204:
142
                Cache::store('openvidu')->forget($this->sessionId);
143
                break;
144
            case 404:
145
                throw new OpenViduSessionNotFoundException();
146
                break;
147
            default:
148
                $result = json_decode($response->getBody()->getContents());
149
                if ($result && property_exists($result, 'message')) {
150
                    throw new OpenViduException($result->message, $response->getStatusCode());
151
                }
152
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
153
        }
154
    }
155
156
    /**
157
     * Updates every property of the Session with the current status it has in
158
     * OpenVidu Server. This is especially useful for getting the list of active
159
     * connections to the Session
160
     * ({@see getActiveConnections()}) and use
161
     * those values to call
162
     * {@see forceDisconnect(Connection)} or
163
     * {@link forceUnpublish(Publisher)}. <br>
164
     *
165
     * To update every Session object owned by OpenVidu object, call
166
     * {@see fetch()}
167
     *
168
     * @return bool true if the Session status has changed with respect to the server,
169
     * false if not. This applies to any property or sub-property of the object
170
     */
171
172
    public function fetch()
173
    {
174
        $response = $this->client->get(Uri::SESSION_URI . '/' . $this->sessionId, [
175
            'headers' => [
176
                'Content-Type' => 'application/x-www-form-urlencoded',
177
                'Accept' => 'application/json',
178
            ]
179
        ]);
180
        if ($response->getStatusCode() === 200) {
181
            $beforeJSON = $this->toJson();
182
            $this->fromJson($response->getBody()->getContents());
183
            $afterJSON = $this->toJson();
184
            if ($beforeJSON !== $afterJSON) {
185
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
186
                return true;
187
            }
188
        }
189
        return false;
190
    }
191
192
193
    /**
194
     * Forces the user with Connection `connectionId` to leave the session. OpenVidu Browser will trigger the proper events on the client-side
195
     * (`streamDestroyed`, `connectionDestroyed`, `sessionDisconnected`) with reason set to `"forceDisconnectByServer"`
196
     *
197
     * You can get `connection` parameter from activeConnections array {@see Connection::getConnectionId()} for getting each `connectionId` property).
198
     * Remember to call {@see fetch()} before to fetch the current actual properties of the Session from OpenVidu Server
199
     *
200
     * @param string $connectionId
201
     * @return bool
202
     * @throws OpenViduException
203
     */
204
    public function forceDisconnect(string $connectionId): bool
205
    {
206
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId . '/connection/' . $connectionId, [
207
            'headers' => [
208
                'Content-Type' => 'application/x-www-form-urlencoded',
209
                'Accept' => 'application/json',
210
            ]
211
        ]);
212
        switch ($response->getStatusCode()) {
213
            case 204:
214
                $connectionClosed = null;
215
                $this->activeConnections = array_filter($this->activeConnections, function (Connection $connection) use ($connectionId) {
216
                    if ($connection->getConnectionId() !== $connectionId) {
217
                        return true;
218
                    } else {
219
                        $connectionClosed = $connection;
0 ignored issues
show
Unused Code introduced by
The assignment to $connectionClosed is dead and can be removed.
Loading history...
220
                        return false;
221
                    }
222
                });
223
                if ($connectionClosed != null) {
0 ignored issues
show
introduced by
The condition $connectionClosed != null is always false.
Loading history...
224
                    foreach ($connectionClosed->getPublishers() as $publisher) {
225
                        foreach ($this->activeConnections as $con) {
226
                            $con->unsubscribe($publisher->getStreamId());
227
                        }
228
                    }
229
                }
230
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
231
                break;
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
232
            case 400:
233
                throw new OpenViduSessionNotFoundException();
234
                break;
235
            case 404:
236
                throw new OpenViduConnectionNotFoundException();
237
                break;
238
            default:
239
                $result = json_decode($response->getBody()->getContents());
240
                if ($result && property_exists($result, 'message')) {
241
                    throw new OpenViduException($result->message, $response->getStatusCode());
242
                }
243
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
244
        }
245
    }
246
247
    /**
248
     * Forces some user to unpublish a Stream. OpenVidu Browser will trigger the
249
     * proper events on the client-side (<code>streamDestroyed</code>) with reason
250
     * set to "forceUnpublishByServer". <br>
251
     *
252
     * You can get <code>streamId</code> parameter with
253
     * {@see Session::getActiveConnections()} and then for
254
     * each Connection you can call
255
     * {@see  Connection::getPublishers()}. Finally
256
     * {@see Publisher::getStreamId()}) will give you the
257
     * <code>streamId</code>. Remember to call
258
     * {@see fetch()} before to fetch the current
259
     * actual properties of the Session from OpenVidu Server
260
     *
261
     * @param string $streamId
262
     * @return void
263
     * @throws OpenViduConnectionNotFoundException
264
     * @throws OpenViduException
265
     * @throws OpenViduSessionNotFoundException
266
     */
267
    public function forceUnpublish(string $streamId)
268
    {
269
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId . '/stream/' . $streamId, [
270
            'headers' => [
271
                'Content-Type' => 'application/x-www-form-urlencoded',
272
                'Accept' => 'application/json',
273
            ]
274
        ]);
275
        switch ($response->getStatusCode()) {
276
            case 204:
277
                foreach ($this->activeConnections as $connection) {
278
                    $connection->unpublish($streamId);
279
                    $connection->unsubscribe($streamId);
280
                }
281
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
282
                break;
283
            case 400:
284
                throw new OpenViduSessionNotFoundException();
285
                break;
286
            case 404:
287
                throw new OpenViduConnectionNotFoundException();
288
                break;
289
            default:
290
                $result = json_decode($response->getBody()->getContents());
291
                if ($result && property_exists($result, 'message')) {
292
                    throw new OpenViduException($result->message, $response->getStatusCode());
293
                }
294
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
295
        }
296
    }
297
298
    /**
299
     * Returns the list of active connections to the session. <strong>This value
300
     * will remain unchanged since the last time method
301
     * {@see fetch()} was called</strong>.
302
     * Exceptions to this rule are:
303
     * <ul>
304
     * <li>Calling {@see Session::forceUnpublish(string)}
305
     * updates each affected Connection status</li>
306
     * <li>Calling {@see Session::forceDisconnect(string)}
307
     * updates each affected Connection status</li>
308
     * </ul>
309
     * <br>
310
     * To get the list of active connections with their current actual value, you
311
     * must call first {@see Session::fetch()} and then
312
     * {@see Session::getActiveConnections()}
313
     */
314
    public function getActiveConnections(): array
315
    {
316
        return $this->activeConnections;
317
    }
318
319
    /**
320
     * Returns the properties defining the session
321
     */
322
    public function getProperties(): SessionProperties
323
    {
324
        return $this->properties;
325
    }
326
327
    /**
328
     * The following values are considered empty:
329
     * <ul><li>"" (an empty string)</li>
330
     * <li>0 (0 as an integer)</li>
331
     * <li>0.0 (0 as a float)</li>
332
     * <li>"0" (0 as a string)</li>
333
     * <li>NULL</li>
334
     * <li>FALSE </li>
335
     * <li>array() (an empty array)</li></ul>
336
     * @return bool
337
     */
338
    private function hasSessionId(): bool
339
    {
340
        return !empty($this->sessionId);
341
    }
342
343
344
    /**
345
     * Returns whether the session is being recorded or not
346
     */
347
    public function isBeingRecorded(): bool
348
    {
349
        return $this->recording;
350
    }
351
352
    /**
353
     * Set value
354
     * @param bool $recording
355
     */
356
    public function setIsBeingRecorded(bool $recording)
357
    {
358
        $this->recording = $recording;
359
        Cache::store('openvidu')->update($this->sessionId, $this->toJson());
360
    }
361
362
363
    /**
364
     * Convert the model instance to JSON.
365
     *
366
     * @param int $options
367
     * @return string
368
     *
369
     */
370
    public function toJson($options = 0): string
371
    {
372
        $json = json_encode($this->jsonSerialize(), $options);
373
        return $json;
374
    }
375
376
    /**
377
     * Specify data which should be serialized to JSON
378
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
379
     * @return mixed data which can be serialized by <b>json_encode</b>,
380
     * which is a value of any type other than a resource.
381
     * @since 5.4.0
382
     */
383
    public function jsonSerialize()
384
    {
385
        return $this->toArray();
386
    }
387
388
    /**
389
     * Convert the model instance to an array.
390
     *
391
     * @return array
392
     */
393
    public function toArray(): array
394
    {
395
        $array = ['sessionId' => $this->sessionId, 'properties' => $this->properties->toArray(), 'recording' => $this->recording, 'createdAt' => $this->createdAt];
396
        foreach ($this->activeConnections as $connection) {
397
            $array['activeConnections'][] = $connection->toArray();
398
        }
399
400
        foreach ($array as $key => $value) {
401
            if (is_null($value) || $value == '')
402
                unset($array[$key]);
403
        }
404
        return $array;
405
    }
406
407
    /**
408
     * @param string $json
409
     * @return Session
410
     */
411
    public function fromJson(string $json): Session
412
    {
413
        return $this->fromArray(json_decode($json, true));
414
    }
415
416
417
    /**
418
     * @param array $sessionArray
419
     * @return Session
420
     */
421
    public function fromArray(array $sessionArray): Session
422
    {
423
        $this->sessionId = $sessionArray['sessionId'];
424
        $this->createdAt =  $sessionArray['createdAt'] ?? null;
425
        $this->recording = $sessionArray['recording'] ??  null;
426
427
        if (array_key_exists('properties', $sessionArray)) {
428
            $this->properties =  SessionPropertiesBuilder::build($sessionArray['properties']);
429
        }
430
431
        $this->activeConnections = [];
432
        if (array_key_exists('connections', $sessionArray)) {
433
            foreach ($sessionArray['connections'] as $connection) {
434
                $publishers = [];
435
                $ensure = $connection['content'] ?? $connection;
436
                foreach ($ensure['publishers'] as $publisher) {
437
                    $publishers[] = PublisherBuilder::build($publisher);
438
                }
439
                $subscribers = [];
440
                foreach ($ensure->subscribers as $subscriber) {
441
                    $subscribers[] = $subscriber->streamId;
442
                }
443
                $this->activeConnections[] = ConnectionBuilder::build($ensure, $publishers, $subscribers);
444
            }
445
            usort($this->activeConnections[], function ($a, $b) {
446
                if ($a->createdAt > $b->createdAt) {
447
                    return 1;
448
                }
449
                if ($b->createdAt > $a->createdAt) {
450
                    return -1;
451
                }
452
                return 0;
453
            });
454
        }
455
        return $this;
456
    }
457
458
    /**
459
     * @return string
460
     * @throws OpenViduException
461
     */
462
    public function __toString(): string
463
    {
464
        return $this->getSessionId();
465
    }
466
}
467