Passed
Push — master ( 277d4a...4e9b37 )
by Albert
06:50 queued 04:31
created

InteractsWithWebsocket::isWebsocketPushPayload()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 4
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SwooleTW\Http\Concerns;
4
5
use Throwable;
6
use Illuminate\Pipeline\Pipeline;
7
use SwooleTW\Http\Server\Sandbox;
8
use SwooleTW\Http\Websocket\Parser;
9
use SwooleTW\Http\Websocket\Pusher;
10
use SwooleTW\Http\Websocket\Websocket;
11
use SwooleTW\Http\Transformers\Request;
12
use SwooleTW\Http\Server\Facades\Server;
13
use SwooleTW\Http\Websocket\HandlerContract;
14
use Illuminate\Contracts\Container\Container;
15
use Swoole\WebSocket\Server as WebsocketServer;
16
use SwooleTW\Http\Websocket\Rooms\RoomContract;
17
use SwooleTW\Http\Exceptions\WebsocketNotSetInConfigException;
18
19
/**
20
 * Trait InteractsWithWebsocket
21
 *
22
 * @property \Illuminate\Contracts\Container\Container $container
23
 * @property \Illuminate\Contracts\Container\Container $app
24
 * @property array $types
25
 */
26
trait InteractsWithWebsocket
27
{
28
    /**
29
     * @var boolean
30
     */
31
    protected $isServerWebsocket = false;
32
33
    /**
34
     * @var \SwooleTW\Http\Websocket\HandlerContract
35
     */
36
    protected $websocketHandler;
37
38
    /**
39
     * @var \SwooleTW\Http\Websocket\Parser
40
     */
41
    protected $payloadParser;
42
43
    /**
44
     * @var SwooleTW\Http\Websocket\Rooms\RoomContract
0 ignored issues
show
Bug introduced by
The type SwooleTW\Http\Concerns\S...cket\Rooms\RoomContract was not found. Did you mean SwooleTW\Http\Websocket\Rooms\RoomContract? If so, make sure to prefix the type with \.
Loading history...
45
     */
46
    protected $websocketRoom;
47
48
    /**
49
     * Websocket server events.
50
     *
51
     * @var array
52
     */
53
    protected $wsEvents = ['open', 'message', 'close'];
54
55
    /**
56
     * "onOpen" listener.
57
     *
58
     * @param \Swoole\Websocket\Server $server
59
     * @param \Swoole\Http\Request $swooleRequest
60
     */
61
    public function onOpen($server, $swooleRequest)
0 ignored issues
show
Unused Code introduced by
The parameter $server is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

61
    public function onOpen(/** @scrutinizer ignore-unused */ $server, $swooleRequest)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
62
    {
63
        $illuminateRequest = Request::make($swooleRequest)->toIlluminate();
64
        $websocket = $this->app->make(Websocket::class);
65
        $sandbox = $this->app->make(Sandbox::class);
66
67
        try {
68
            $websocket->reset(true)->setSender($swooleRequest->fd);
69
            // set currnt request to sandbox
70
            $sandbox->setRequest($illuminateRequest);
71
            // enable sandbox
72
            $sandbox->enable();
73
            // check if socket.io connection established
74
            if (! $this->websocketHandler->onOpen($swooleRequest->fd, $illuminateRequest)) {
75
                return;
76
            }
77
            // trigger 'connect' websocket event
78
            if ($websocket->eventExists('connect')) {
79
                // set sandbox container to websocket pipeline
80
                $websocket->setContainer($sandbox->getApplication());
81
                $websocket->call('connect', $illuminateRequest);
82
            }
83
        } catch (Throwable $e) {
84
            $this->logServerError($e);
0 ignored issues
show
Bug introduced by
It seems like logServerError() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

84
            $this->/** @scrutinizer ignore-call */ 
85
                   logServerError($e);
Loading history...
85
        } finally {
86
            // disable and recycle sandbox resource
87
            $sandbox->disable();
88
        }
89
    }
90
91
    /**
92
     * "onMessage" listener.
93
     *
94
     * @param \Swoole\Websocket\Server $server
95
     * @param \Swoole\Websocket\Frame $frame
96
     */
97
    public function onMessage($server, $frame)
98
    {
99
        // execute parser strategies and skip non-message packet
100
        if ($this->payloadParser->execute($server, $frame)) {
101
            return;
102
        }
103
104
        $websocket = $this->app->make(Websocket::class);
105
        $sandbox = $this->app->make(Sandbox::class);
106
107
        try {
108
            // decode raw message via parser
109
            $payload = $this->payloadParser->decode($frame);
110
111
            $websocket->reset(true)->setSender($frame->fd);
112
113
            // enable sandbox
114
            $sandbox->enable();
115
116
            // dispatch message to registered event callback
117
            ['event' => $event, 'data' => $data] = $payload;
118
            $websocket->eventExists($event)
119
                ? $websocket->call($event, $data)
120
                : $this->websocketHandler->onMessage($frame);
121
        } catch (Throwable $e) {
122
            $this->logServerError($e);
123
        } finally {
124
            // disable and recycle sandbox resource
125
            $sandbox->disable();
126
        }
127
    }
128
129
    /**
130
     * "onClose" listener.
131
     *
132
     * @param \Swoole\Websocket\Server $server
133
     * @param int $fd
134
     * @param int $reactorId
135
     */
136
    public function onClose($server, $fd, $reactorId)
137
    {
138
        if (! $this->isServerWebsocket($fd) || ! $server instanceof WebsocketServer) {
139
            return;
140
        }
141
142
        $websocket = $this->app->make(Websocket::class);
143
144
        try {
145
            $websocket->reset(true)->setSender($fd);
146
            // trigger 'disconnect' websocket event
147
            if ($websocket->eventExists('disconnect')) {
148
                $websocket->call('disconnect');
149
            } else {
150
                $this->websocketHandler->onClose($fd, $reactorId);
151
            }
152
            // leave all rooms
153
            $websocket->leave();
154
        } catch (Throwable $e) {
155
            $this->logServerError($e);
156
        }
157
    }
158
159
    /**
160
     * Indicates if a packet is websocket push action.
161
     *
162
     * @param mixed
163
     */
164
    protected function isWebsocketPushPacket($packet)
165
    {
166
        if (! is_array($packet)) {
167
            return false;
168
        }
169
170
        return $this->isWebsocket
171
            && array_key_exists('action', $packet)
172
            && $packet['action'] === Websocket::PUSH_ACTION;
173
    }
174
175
176
    /**
177
     * Push websocket message to clients.
178
     *
179
     * @param \Swoole\Websocket\Server $server
180
     * @param mixed $data
181
     */
182
    public function pushMessage($server, array $data)
183
    {
184
        $pusher = Pusher::make($data, $server);
185
        $pusher->push($this->payloadParser->encode(
186
            $pusher->getEvent(),
187
            $pusher->getMessage()
188
        ));
189
    }
190
191
    /**
192
     * Set frame parser for websocket.
193
     *
194
     * @param \SwooleTW\Http\Websocket\Parser $payloadParser
195
     *
196
     * @return \SwooleTW\Http\Concerns\InteractsWithWebsocket
197
     */
198
    public function setPayloadParser(Parser $payloadParser)
199
    {
200
        $this->payloadParser = $payloadParser;
201
202
        return $this;
203
    }
204
205
    /**
206
     * Get frame parser for websocket.
207
     */
208
    public function getPayloadParser()
209
    {
210
        return $this->payloadParser;
211
    }
212
213
    /**
214
     * Prepare settings if websocket is enabled.
215
     */
216
    protected function prepareWebsocket()
217
    {
218
        $config = $this->container->make('config');
219
        $isWebsocket = $config->get('swoole_http.websocket.enabled');
220
        $parser = $config->get('swoole_websocket.parser');
221
222
        if ($isWebsocket) {
223
            $this->events = array_merge($this->events ?? [], $this->wsEvents);
0 ignored issues
show
Bug Best Practice introduced by
The property events does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
224
            $this->isServerWebsocket = true;
225
            $this->prepareWebsocketRoom();
226
            $this->setPayloadParser(new $parser);
227
        }
228
    }
229
230
    /**
231
     * Check if it's a websocket fd.
232
     *
233
     * @param int $fd
234
     *
235
     * @return bool
236
     */
237
    protected function isServerWebsocket(int $fd): bool
238
    {
239
        return (bool) $this->container->make(Server::class)
240
            ->connection_info($fd)['websocket_status'] ?? false;
0 ignored issues
show
Bug introduced by
The method connection_info() does not exist on SwooleTW\Http\Server\Facades\Server. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

240
            ->/** @scrutinizer ignore-call */ connection_info($fd)['websocket_status'] ?? false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
241
    }
242
243
    /**
244
     * Prepare websocket handler for onOpen and onClose callback.
245
     *
246
     * @throws \Exception
247
     */
248
    protected function prepareWebsocketHandler()
249
    {
250
        $handlerClass = $this->container->make('config')->get('swoole_websocket.handler');
251
252
        if (! $handlerClass) {
253
            throw new WebsocketNotSetInConfigException;
254
        }
255
256
        $this->setWebsocketHandler($this->app->make($handlerClass));
257
    }
258
259
    /**
260
     * Prepare websocket room.
261
     */
262
    protected function prepareWebsocketRoom()
263
    {
264
        $config = $this->container->make('config');
265
        $driver = $config->get('swoole_websocket.default');
266
        $websocketConfig = $config->get("swoole_websocket.settings.{$driver}");
267
        $className = $config->get("swoole_websocket.drivers.{$driver}");
268
269
        $this->websocketRoom = new $className($websocketConfig);
270
        $this->websocketRoom->prepare();
271
    }
272
273
    /**
274
     * Set websocket handler.
275
     *
276
     * @param \SwooleTW\Http\Websocket\HandlerContract $handler
277
     *
278
     * @return \SwooleTW\Http\Concerns\InteractsWithWebsocket
279
     */
280
    public function setWebsocketHandler(HandlerContract $handler)
281
    {
282
        $this->websocketHandler = $handler;
283
284
        return $this;
285
    }
286
287
    /**
288
     * Get websocket handler.
289
     *
290
     * @return \SwooleTW\Http\Websocket\HandlerContract
291
     */
292
    public function getWebsocketHandler(): HandlerContract
293
    {
294
        return $this->websocketHandler;
295
    }
296
297
    /**
298
     * @param string $class
299
     * @param array $settings
300
     *
301
     * @return \SwooleTW\Http\Websocket\Rooms\RoomContract
302
     */
303
    protected function createRoom(string $class, array $settings): RoomContract
304
    {
305
        return new $class($settings);
306
    }
307
308
    /**
309
     * Bind room instance to Laravel app container.
310
     */
311
    protected function bindRoom(): void
312
    {
313
        $this->app->singleton(RoomContract::class, function (Container $app) {
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

313
        $this->app->singleton(RoomContract::class, function (/** @scrutinizer ignore-unused */ Container $app) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
314
            return $this->websocketRoom;
315
        });
316
317
        $this->app->alias(RoomContract::class, 'swoole.room');
318
    }
319
320
    /**
321
     * Bind websocket instance to Laravel app container.
322
     */
323
    protected function bindWebsocket()
324
    {
325
        $this->app->singleton(Websocket::class, function (Container $app) {
326
            return new Websocket($app->make(RoomContract::class), new Pipeline($app));
327
        });
328
329
        $this->app->alias(Websocket::class, 'swoole.websocket');
330
    }
331
332
    /**
333
     * Load websocket routes file.
334
     */
335
    protected function loadWebsocketRoutes()
336
    {
337
        $routePath = $this->container->make('config')
338
            ->get('swoole_websocket.route_file');
339
340
        if (! file_exists($routePath)) {
341
            $routePath = __DIR__ . '/../../routes/websocket.php';
342
        }
343
344
        return require $routePath;
345
    }
346
347
    /**
348
     * Indicates if the payload is websocket push.
349
     *
350
     * @param mixed $payload
351
     *
352
     * @return boolean
353
     */
354
    public function isWebsocketPushPayload($payload): bool
355
    {
356
        if (! is_array($payload)) {
357
            return false;
358
        }
359
360
        return $this->isServerWebsocket
361
            && ($payload['action'] ?? null) === Websocket::PUSH_ACTION
362
            && array_key_exists('data', $payload);
363
    }
364
}
365