Passed
Push — master ( 832284...1e6bd5 )
by Jacobo
02:57
created

Session::forceDisconnect()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 1
Metric Value
cc 4
eloc 17
c 6
b 0
f 1
nc 4
nop 1
dl 0
loc 21
rs 9.7
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 : new SessionProperties(MediaMode::ROUTED, RecordingMode::MANUAL, OutputMode::COMPOSED, RecordingLayout::BEST_FIT);
58
        $this->sessionId = $this->getSessionId();
59
    }
60
61
    /**
62
     * @return string
63
     * @throws OpenViduException
64
     */
65
    public function getSessionId()
66
    {
67
        if (empty($this->sessionId)) {
68
            $response = $this->client->post(Uri::SESSION_URI, [
69
                RequestOptions::JSON => $this->properties->toArray()
70
            ]);
71
            switch ($response->getStatusCode()) {
72
                case 200:
73
                    return json_decode($response->getBody()->getContents())->id;
74
                case 409:
75
                    return $this->properties->getCustomSessionId();
76
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
77
                default:
78
                    throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
79
                    break;
80
            }
81
        } else {
82
            return $this->sessionId;
83
        }
84
    }
85
86
    /**
87
     ** Gets a new token associated to Session object with default values for
88
     * {@see TokenOptions}. This always translates into a
89
     * new request to OpenVidu Server
90
     *
91
     * @param TokenOptions|null $tokenOptions
92
     * @return string The generated token
93
     * @throws OpenViduException
94
     */
95
    public function generateToken(?TokenOptions $tokenOptions = null)
96
    {
97
        $this->getSessionId();
98
        try {
99
            if (!$tokenOptions) {
100
                $tokenOptions = new TokenOptions(OpenViduRole::PUBLISHER);;
101
            }
102
            $response = $this->client->post(Uri::TOKEN_URI, [
103
                RequestOptions::JSON => array_merge($tokenOptions->toArray(), ['session' => $this->sessionId])
104
            ]);
105
            return json_decode($response->getBody()->getContents());
106
        } catch (Exception $e) {
107
            throw new OpenViduTokenCantCreateException($e->getMessage(), $e);
108
        }
109
    }
110
111
    /**
112
     * Gracefully closes the Session: unpublishes all streams and evicts every
113
     * participant
114
     * @throws OpenViduException
115
     */
116
    public function close()
117
    {
118
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId);
119
        switch ($response->getStatusCode()) {
120
            case 204:
121
                Cache::store('openvidu')->forget($this->sessionId);
122
                break;
123
            case 404:
124
                throw new OpenViduSessionNotFoundException();
125
                break;
126
            default:
127
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
128
        }
129
    }
130
131
    /**
132
     * Updates every property of the Session with the current status it has in
133
     * OpenVidu Server. This is especially useful for getting the list of active
134
     * connections to the Session
135
     * ({@see getActiveConnections()}) and use
136
     * those values to call
137
     * {@see forceDisconnect(Connection)} or
138
     * {@link forceUnpublish(Publisher)}. <br>
139
     *
140
     * To update every Session object owned by OpenVidu object, call
141
     * {@see fetch()}
142
     *
143
     * @return bool true if the Session status has changed with respect to the server,
144
     * false if not. This applies to any property or sub-property of the object
145
     */
146
147
    public function fetch()
148
    {
149
        $response = $this->client->get(Uri::SESSION_URI . '/' . $this->sessionId, [
150
            'headers' => [
151
                'Content-Type' => 'application/x-www-form-urlencoded',
152
                'Accept' => 'application/json',
153
            ]
154
        ]);
155
        if ($response->getStatusCode() === 200) {
156
            $beforeJSON = $this->toJson();
157
            $this->fromJson($response->getBody()->getContents());
158
            $afterJSON = $this->toJson();
159
            if ($beforeJSON !== $afterJSON) {
160
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
161
                return true;
162
            }
163
        }
164
        return false;
165
    }
166
167
    /**
168
     * Convert the model instance to JSON.
169
     *
170
     * @param int $options
171
     * @return string
172
     *
173
     */
174
    public function toJson($options = 0): string
175
    {
176
        $json = json_encode($this->jsonSerialize(), $options);
177
        return $json;
178
    }
179
180
    /**
181
     * Specify data which should be serialized to JSON
182
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
183
     * @return mixed data which can be serialized by <b>json_encode</b>,
184
     * which is a value of any type other than a resource.
185
     * @since 5.4.0
186
     */
187
    public function jsonSerialize()
188
    {
189
        return $this->toArray();
190
    }
191
192
    /**
193
     * Convert the model instance to an array.
194
     *
195
     * @return array
196
     */
197
    public function toArray(): array
198
    {
199
        $array = ['sessionId' => $this->sessionId, 'properties' => $this->properties->toArray(), 'recording' => $this->recording, 'createdAt' => $this->createdAt];
200
        foreach ($this->activeConnections as $connection) {
201
            $array['activeConnections'][] = $connection->toArray();
202
        }
203
204
        foreach ($array as $key => $value) {
205
            if (is_null($value) || $value == '')
206
                unset($array[$key]);
207
        }
208
        return $array;
209
    }
210
211
    /**
212
     * @param string $json
213
     * @return Session
214
     */
215
    public function fromJson(string $json): Session
216
    {
217
        return $this->fromArray(json_decode($json, true));
218
    }
219
220
    /**
221
     * @param array $sessionArray
222
     * @return Session
223
     */
224
    public function fromArray(array $sessionArray): Session
225
    {
226
        $this->sessionId = $sessionArray['sessionId'];
227
        $this->createdAt = $sessionArray['createdAt'] ?? null;
228
        $this->recording = $sessionArray['recording'] ?? null;
229
230
        if (array_key_exists('properties', $sessionArray)) {
231
            $this->properties = SessionPropertiesBuilder::build($sessionArray['properties']);
232
        }
233
234
        $this->activeConnections = [];
235
        if (array_key_exists('connections', $sessionArray)) {
236
            foreach ($sessionArray['connections'] as $connection) {
237
                $publishers = [];
238
                $ensure = $connection['content'] ?? $connection;
239
                foreach ($ensure['publishers'] as $publisher) {
240
                    $publishers[] = PublisherBuilder::build($publisher);
241
                }
242
                $subscribers = [];
243
                foreach ($ensure->subscribers as $subscriber) {
244
                    $subscribers[] = $subscriber->streamId;
245
                }
246
                $this->activeConnections[] = ConnectionBuilder::build($ensure, $publishers, $subscribers);
247
            }
248
            usort($this->activeConnections[], function ($a, $b) {
249
                if ($a->createdAt > $b->createdAt) {
250
                    return 1;
251
                }
252
                if ($b->createdAt > $a->createdAt) {
253
                    return -1;
254
                }
255
                return 0;
256
            });
257
        }
258
        return $this;
259
    }
260
261
    /**
262
     * Forces the user with Connection `connectionId` to leave the session. OpenVidu Browser will trigger the proper events on the client-side
263
     * (`streamDestroyed`, `connectionDestroyed`, `sessionDisconnected`) with reason set to `"forceDisconnectByServer"`
264
     *
265
     *
266
     * @param string $connectionId
267
     * @return bool
268
     * @throws OpenViduException
269
     */
270
    public function forceDisconnect(string $connectionId): bool
271
    {
272
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId . '/connection/' . $connectionId, [
273
            'headers' => [
274
                'Content-Type' => 'application/x-www-form-urlencoded',
275
                'Accept' => 'application/json',
276
            ]
277
        ]);
278
        switch ($response->getStatusCode()) {
279
            case 204:
280
                $this->leaveSession($connectionId);
281
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
282
                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...
283
            case 400:
284
                throw new OpenViduSessionNotFoundException();
285
                break;
286
            case 404:
287
                throw new OpenViduConnectionNotFoundException();
288
                break;
289
            default:
290
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
291
        }
292
    }
293
294
    /**
295
     * Get `connection` parameter from activeConnections array {@see Connection::getConnectionId()} for getting each `connectionId` property).
296
     * Remember to call {@see fetch()} before to fetch the current actual properties of the Session from OpenVidu Server
297
     * @param string $connectionId
298
     */
299
300
    private function leaveSession(string $connectionId)
301
    {
302
        $connectionClosed = null;
303
        $this->activeConnections = array_filter($this->activeConnections, function (Connection $connection) use (&$connectionClosed, $connectionId) {
304
            if ($connection->getConnectionId() !== $connectionId) {
305
                return true;
306
            }
307
            $connectionClosed = $connection;
308
            return false;
309
        });
310
        if ($connectionClosed != null) {
311
            foreach ($connectionClosed->getPublishers() as $publisher) {
312
                foreach ($this->activeConnections as $con) {
313
                    $con->unsubscribe($publisher->getStreamId());
314
                }
315
            }
316
        }
317
    }
318
319
    /**
320
     * Forces some user to unpublish a Stream. OpenVidu Browser will trigger the
321
     * proper events on the client-side (<code>streamDestroyed</code>) with reason
322
     * set to "forceUnpublishByServer". <br>
323
     *
324
     * You can get <code>streamId</code> parameter with
325
     * {@see Session::getActiveConnections()} and then for
326
     * each Connection you can call
327
     * {@see  Connection::getPublishers()}. Finally
328
     * {@see Publisher::getStreamId()}) will give you the
329
     * <code>streamId</code>. Remember to call
330
     * {@see fetch()} before to fetch the current
331
     * actual properties of the Session from OpenVidu Server
332
     *
333
     * @param string $streamId
334
     * @return void
335
     * @throws OpenViduConnectionNotFoundException
336
     * @throws OpenViduException
337
     * @throws OpenViduSessionNotFoundException
338
     */
339
    public function forceUnpublish(string $streamId)
340
    {
341
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId . '/stream/' . $streamId, [
342
            'headers' => [
343
                'Content-Type' => 'application/x-www-form-urlencoded',
344
                'Accept' => 'application/json',
345
            ]
346
        ]);
347
        switch ($response->getStatusCode()) {
348
            case 204:
349
                foreach ($this->activeConnections as $connection) {
350
                    $connection->unpublish($streamId);
351
                    $connection->unsubscribe($streamId);
352
                }
353
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
354
                break;
355
            case 400:
356
                throw new OpenViduSessionNotFoundException();
357
                break;
358
            case 404:
359
                throw new OpenViduConnectionNotFoundException();
360
                break;
361
            default:
362
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
363
        }
364
    }
365
366
    /**
367
     * Returns the list of active connections to the session. <strong>This value
368
     * will remain unchanged since the last time method
369
     * {@see fetch()} was called</strong>.
370
     * Exceptions to this rule are:
371
     * <ul>
372
     * <li>Calling {@see Session::forceUnpublish(string)}
373
     * updates each affected Connection status</li>
374
     * <li>Calling {@see Session::forceDisconnect(string)}
375
     * updates each affected Connection status</li>
376
     * </ul>
377
     * <br>
378
     * To get the list of active connections with their current actual value, you
379
     * must call first {@see Session::fetch()} and then
380
     * {@see Session::getActiveConnections()}
381
     */
382
    public function getActiveConnections(): array
383
    {
384
        return $this->activeConnections;
385
    }
386
387
    /**
388
     * Returns whether the session is being recorded or not
389
     */
390
    public function isBeingRecorded(): bool
391
    {
392
        return $this->recording;
393
    }
394
395
    /**
396
     * Set value
397
     * @param bool $recording
398
     */
399
    public function setIsBeingRecorded(bool $recording)
400
    {
401
        $this->recording = $recording;
402
        Cache::store('openvidu')->update($this->sessionId, $this->toJson());
403
    }
404
405
    /**
406
     * @return string
407
     * @throws OpenViduException
408
     */
409
    public function __toString(): string
410
    {
411
        return $this->getSessionId();
412
    }
413
}
414