Passed
Push — master ( c03036...d048db )
by Shahrad
02:03
created
examples/websocket/coinmarketcap-ticker.php 2 patches
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -10,58 +10,58 @@
 block discarded – undo
10 10
 $SocketClient = new WebSocket();
11 11
 
12 12
 $SocketClient->onWhile = function (WebSocket $socket) use ($close_time) {
13
-	if (time() >= $close_time) {
14
-		$socket->close();
15
-	}
13
+    if (time() >= $close_time) {
14
+        $socket->close();
15
+    }
16 16
 };
17 17
 
18 18
 $SocketClient->onOpen = function (WebSocket $socket) {
19
-	echo sprintf(
20
-		'<pre><b>%s</b>: Connected to %s</pre><br/>',
21
-		date('Y-m-d H:i:s'),
22
-		$socket->getSocketUrl()
23
-	);
19
+    echo sprintf(
20
+        '<pre><b>%s</b>: Connected to %s</pre><br/>',
21
+        date('Y-m-d H:i:s'),
22
+        $socket->getSocketUrl()
23
+    );
24 24
 
25
-	$socket->send(json_encode([
26
-		'method' => 'subscribe',
27
-		'id' => 'price',
28
-		'data' => [
29
-			'cryptoIds' => [1, 1027, 825, 3408, 1839, 4687, 52, 2010, 5426],
30
-			'index' => 'detail'
31
-		]
32
-	]));
25
+    $socket->send(json_encode([
26
+        'method' => 'subscribe',
27
+        'id' => 'price',
28
+        'data' => [
29
+            'cryptoIds' => [1, 1027, 825, 3408, 1839, 4687, 52, 2010, 5426],
30
+            'index' => 'detail'
31
+        ]
32
+    ]));
33 33
 };
34 34
 
35 35
 $SocketClient->onClose = function (WebSocket $socket, int $closeStatus) {
36
-	echo sprintf(
37
-		'<pre><b>%s</b>: Disconnected with status: %s</pre><br/>',
38
-		date('Y-m-d H:i:s'),
39
-		$closeStatus
40
-	);
36
+    echo sprintf(
37
+        '<pre><b>%s</b>: Disconnected with status: %s</pre><br/>',
38
+        date('Y-m-d H:i:s'),
39
+        $closeStatus
40
+    );
41 41
 };
42 42
 
43 43
 $SocketClient->onMessage = function (WebSocket $socket, string $message) {
44
-	$data = json_decode($message, true);
45
-	if (isset($data['id']) && $data['id'] == "price") {
46
-		echo sprintf(
47
-			'<pre><b>%s</b>: %s</pre><br/>',
48
-			date('Y-m-d H:i:s'),
49
-			$message
50
-		);
51
-	}
44
+    $data = json_decode($message, true);
45
+    if (isset($data['id']) && $data['id'] == "price") {
46
+        echo sprintf(
47
+            '<pre><b>%s</b>: %s</pre><br/>',
48
+            date('Y-m-d H:i:s'),
49
+            $message
50
+        );
51
+    }
52 52
 };
53 53
 
54 54
 $SocketClient->onError = function (WebSocket $socket, WebSocketException $exception) {
55
-	echo sprintf(
56
-		"<pre>%s: Error: %s<br>File: %s:%s<br></pre><br>",
57
-		date('Y-m-d H:i:s'),
58
-		$exception->getMessage(),
59
-		$exception->getFile(),
60
-		$exception->getLine()
61
-	);
55
+    echo sprintf(
56
+        "<pre>%s: Error: %s<br>File: %s:%s<br></pre><br>",
57
+        date('Y-m-d H:i:s'),
58
+        $exception->getMessage(),
59
+        $exception->getFile(),
60
+        $exception->getLine()
61
+    );
62 62
 };
63 63
 
64 64
 $SocketClient->connect(
65
-	'wss://stream.coinmarketcap.com/price/latest',
66
-	(new WebSocketConfig())->setFragmentSize(8096)->setTimeout(15)
65
+    'wss://stream.coinmarketcap.com/price/latest',
66
+    (new WebSocketConfig())->setFragmentSize(8096)->setTimeout(15)
67 67
 );
68 68
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -9,13 +9,13 @@  discard block
 block discarded – undo
9 9
 $close_time = time() + 10;
10 10
 $SocketClient = new WebSocket();
11 11
 
12
-$SocketClient->onWhile = function (WebSocket $socket) use ($close_time) {
12
+$SocketClient->onWhile = function(WebSocket $socket) use ($close_time) {
13 13
 	if (time() >= $close_time) {
14 14
 		$socket->close();
15 15
 	}
16 16
 };
17 17
 
18
-$SocketClient->onOpen = function (WebSocket $socket) {
18
+$SocketClient->onOpen = function(WebSocket $socket) {
19 19
 	echo sprintf(
20 20
 		'<pre><b>%s</b>: Connected to %s</pre><br/>',
21 21
 		date('Y-m-d H:i:s'),
@@ -32,7 +32,7 @@  discard block
 block discarded – undo
32 32
 	]));
33 33
 };
34 34
 
35
-$SocketClient->onClose = function (WebSocket $socket, int $closeStatus) {
35
+$SocketClient->onClose = function(WebSocket $socket, int $closeStatus) {
36 36
 	echo sprintf(
37 37
 		'<pre><b>%s</b>: Disconnected with status: %s</pre><br/>',
38 38
 		date('Y-m-d H:i:s'),
@@ -40,7 +40,7 @@  discard block
 block discarded – undo
40 40
 	);
41 41
 };
42 42
 
43
-$SocketClient->onMessage = function (WebSocket $socket, string $message) {
43
+$SocketClient->onMessage = function(WebSocket $socket, string $message) {
44 44
 	$data = json_decode($message, true);
45 45
 	if (isset($data['id']) && $data['id'] == "price") {
46 46
 		echo sprintf(
@@ -51,7 +51,7 @@  discard block
 block discarded – undo
51 51
 	}
52 52
 };
53 53
 
54
-$SocketClient->onError = function (WebSocket $socket, WebSocketException $exception) {
54
+$SocketClient->onError = function(WebSocket $socket, WebSocketException $exception) {
55 55
 	echo sprintf(
56 56
 		"<pre>%s: Error: %s<br>File: %s:%s<br></pre><br>",
57 57
 		date('Y-m-d H:i:s'),
Please login to merge, or discard this patch.
examples/websocket/anonymous-callable.php 2 patches
Indentation   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -4,17 +4,17 @@
 block discarded – undo
4 4
 $SocketClient = new \EasyHttp\WebSocket();
5 5
 
6 6
 $SocketClient->onOpen = function (\EasyHttp\WebSocket $socket) {
7
-	echo "Connected to server<br>";
8
-	$socket->send("Hello World");
7
+    echo "Connected to server<br>";
8
+    $socket->send("Hello World");
9 9
 };
10 10
 
11 11
 $SocketClient->onMessage = function (\EasyHttp\WebSocket $socket, $message) {
12
-	echo $message . "<br>";
13
-	$socket->close();
12
+    echo $message . "<br>";
13
+    $socket->close();
14 14
 };
15 15
 
16 16
 $SocketClient->onClose = function (\EasyHttp\WebSocket $socket, int $closeStatus) {
17
-	echo "Disconnected with status: $closeStatus<br>";
17
+    echo "Disconnected with status: $closeStatus<br>";
18 18
 };
19 19
 
20 20
 $SocketClient->connect('wss://socket.litehex.com/', new \EasyHttp\WebSocketConfig());
21 21
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -3,17 +3,17 @@
 block discarded – undo
3 3
 
4 4
 $SocketClient = new \EasyHttp\WebSocket();
5 5
 
6
-$SocketClient->onOpen = function (\EasyHttp\WebSocket $socket) {
6
+$SocketClient->onOpen = function(\EasyHttp\WebSocket $socket) {
7 7
 	echo "Connected to server<br>";
8 8
 	$socket->send("Hello World");
9 9
 };
10 10
 
11
-$SocketClient->onMessage = function (\EasyHttp\WebSocket $socket, $message) {
11
+$SocketClient->onMessage = function(\EasyHttp\WebSocket $socket, $message) {
12 12
 	echo $message . "<br>";
13 13
 	$socket->close();
14 14
 };
15 15
 
16
-$SocketClient->onClose = function (\EasyHttp\WebSocket $socket, int $closeStatus) {
16
+$SocketClient->onClose = function(\EasyHttp\WebSocket $socket, int $closeStatus) {
17 17
 	echo "Disconnected with status: $closeStatus<br>";
18 18
 };
19 19
 
Please login to merge, or discard this patch.
src/Contracts/CommonsContract.php 1 patch
Indentation   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -12,58 +12,58 @@
 block discarded – undo
12 12
 interface CommonsContract
13 13
 {
14 14
 
15
-	// DADA types
16
-	public const EVENT_TYPE_PING = 'ping';
17
-	public const EVENT_TYPE_PONG = 'pong';
18
-	public const EVENT_TYPE_TEXT = 'text';
19
-	public const EVENT_TYPE_CLOSE = 'close';
20
-	public const EVENT_TYPE_BINARY = 'binary';
21
-	public const EVENT_TYPE_CONTINUATION = 'continuation';
15
+    // DADA types
16
+    public const EVENT_TYPE_PING = 'ping';
17
+    public const EVENT_TYPE_PONG = 'pong';
18
+    public const EVENT_TYPE_TEXT = 'text';
19
+    public const EVENT_TYPE_CLOSE = 'close';
20
+    public const EVENT_TYPE_BINARY = 'binary';
21
+    public const EVENT_TYPE_CONTINUATION = 'continuation';
22 22
 
23
-	public const MAP_EVENT_TYPE_TO_METHODS = [
24
-		self::EVENT_TYPE_TEXT => 'onMessage',
25
-		self::EVENT_TYPE_PING => 'onPing',
26
-		self::EVENT_TYPE_PONG => 'onPong',
27
-	];
23
+    public const MAP_EVENT_TYPE_TO_METHODS = [
24
+        self::EVENT_TYPE_TEXT => 'onMessage',
25
+        self::EVENT_TYPE_PING => 'onPing',
26
+        self::EVENT_TYPE_PONG => 'onPong',
27
+    ];
28 28
 
29
-	// DECODE FRAMES
30
-	public const DECODE_TEXT = 1;
31
-	public const DECODE_BINARY = 2;
32
-	public const DECODE_CLOSE = 8;
33
-	public const DECODE_PING = 9;
34
-	public const DECODE_PONG = 10;
29
+    // DECODE FRAMES
30
+    public const DECODE_TEXT = 1;
31
+    public const DECODE_BINARY = 2;
32
+    public const DECODE_CLOSE = 8;
33
+    public const DECODE_PING = 9;
34
+    public const DECODE_PONG = 10;
35 35
 
36
-	// ENCODE FRAMES
37
-	public const ENCODE_TEXT = 129;
38
-	public const ENCODE_CLOSE = 136;
39
-	public const ENCODE_PING = 137;
40
-	public const ENCODE_PONG = 138;
36
+    // ENCODE FRAMES
37
+    public const ENCODE_TEXT = 129;
38
+    public const ENCODE_CLOSE = 136;
39
+    public const ENCODE_PING = 137;
40
+    public const ENCODE_PONG = 138;
41 41
 
42
-	// MASKS
43
-	public const MASK_125 = 125;
44
-	public const MASK_126 = 126;
45
-	public const MASK_127 = 127;
46
-	public const MASK_128 = 128;
47
-	public const MASK_254 = 254;
48
-	public const MASK_255 = 255;
42
+    // MASKS
43
+    public const MASK_125 = 125;
44
+    public const MASK_126 = 126;
45
+    public const MASK_127 = 127;
46
+    public const MASK_128 = 128;
47
+    public const MASK_254 = 254;
48
+    public const MASK_255 = 255;
49 49
 
50
-	// PAYLOADS
51
-	public const PAYLOAD_CHUNK = 8;
52
-	public const PAYLOAD_MAX_BITS = 65535;
50
+    // PAYLOADS
51
+    public const PAYLOAD_CHUNK = 8;
52
+    public const PAYLOAD_MAX_BITS = 65535;
53 53
 
54
-	// transfer protocol-level errors
55
-	public const SERVER_COULD_NOT_BIND_TO_SOCKET = 101;
56
-	public const SERVER_SELECT_ERROR = 102;
57
-	public const SERVER_HEADERS_NOT_SET = 103;
58
-	public const CLIENT_COULD_NOT_OPEN_SOCKET = 104;
59
-	public const CLIENT_INCORRECT_SCHEME = 105;
60
-	public const CLIENT_INVALID_UPGRADE_RESPONSE = 106;
61
-	public const CLIENT_INVALID_STREAM_CONTEXT = 107;
62
-	public const CLIENT_BAD_OPCODE = 108;
63
-	public const CLIENT_COULD_ONLY_WRITE_LESS = 109;
64
-	public const CLIENT_BROKEN_FRAME = 110;
65
-	public const CLIENT_EMPTY_READ = 111;
66
-	public const SERVER_INVALID_STREAM_CONTEXT = 112;
67
-	public const CLIENT_CONNECTION_NOT_ESTABLISHED = 113;
54
+    // transfer protocol-level errors
55
+    public const SERVER_COULD_NOT_BIND_TO_SOCKET = 101;
56
+    public const SERVER_SELECT_ERROR = 102;
57
+    public const SERVER_HEADERS_NOT_SET = 103;
58
+    public const CLIENT_COULD_NOT_OPEN_SOCKET = 104;
59
+    public const CLIENT_INCORRECT_SCHEME = 105;
60
+    public const CLIENT_INVALID_UPGRADE_RESPONSE = 106;
61
+    public const CLIENT_INVALID_STREAM_CONTEXT = 107;
62
+    public const CLIENT_BAD_OPCODE = 108;
63
+    public const CLIENT_COULD_ONLY_WRITE_LESS = 109;
64
+    public const CLIENT_BROKEN_FRAME = 110;
65
+    public const CLIENT_EMPTY_READ = 111;
66
+    public const SERVER_INVALID_STREAM_CONTEXT = 112;
67
+    public const CLIENT_CONNECTION_NOT_ESTABLISHED = 113;
68 68
 
69 69
 }
70 70
\ No newline at end of file
Please login to merge, or discard this patch.
src/Contracts/WscCommonsContract.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -12,25 +12,25 @@
 block discarded – undo
12 12
 interface WscCommonsContract
13 13
 {
14 14
 
15
-	public const TCP_SCHEME = 'tcp://';
15
+    public const TCP_SCHEME = 'tcp://';
16 16
 
17
-	public const MAX_BYTES_READ = 65535;
18
-	public const DEFAULT_TIMEOUT = 5;
19
-	public const DEFAULT_FRAGMENT_SIZE = 4096;
20
-	public const DEFAULT_RESPONSE_HEADER = 8192;
21
-	public const SEC_WEBSOCKET_ACCEPT_PTTRN = '/Sec-WebSocket-Accept:\s(.*)$/mUi';
22
-	public const SERVER_KEY_ACCEPT = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
23
-	public const PROXY_MATCH_RESP = '/^HTTP\/\d\.\d 200/';
17
+    public const MAX_BYTES_READ = 65535;
18
+    public const DEFAULT_TIMEOUT = 5;
19
+    public const DEFAULT_FRAGMENT_SIZE = 4096;
20
+    public const DEFAULT_RESPONSE_HEADER = 8192;
21
+    public const SEC_WEBSOCKET_ACCEPT_PTTRN = '/Sec-WebSocket-Accept:\s(.*)$/mUi';
22
+    public const SERVER_KEY_ACCEPT = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
23
+    public const PROXY_MATCH_RESP = '/^HTTP\/\d\.\d 200/';
24 24
 
25
-	// MASKS
26
-	public const MASK_125 = 125;
27
-	public const MASK_126 = 126;
28
-	public const MASK_127 = 127;
29
-	public const MASK_128 = 128;
30
-	public const MASK_254 = 254;
31
-	public const MASK_255 = 255;
25
+    // MASKS
26
+    public const MASK_125 = 125;
27
+    public const MASK_126 = 126;
28
+    public const MASK_127 = 127;
29
+    public const MASK_128 = 128;
30
+    public const MASK_254 = 254;
31
+    public const MASK_255 = 255;
32 32
 
33
-	public const KEY_GEN_LENGTH = 16;
34
-	public const DEFAULT_PING_INTERVAL = 5;
33
+    public const KEY_GEN_LENGTH = 16;
34
+    public const DEFAULT_PING_INTERVAL = 5;
35 35
 
36 36
 }
Please login to merge, or discard this patch.
src/Contracts/MessageContract.php 1 patch
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -15,11 +15,11 @@
 block discarded – undo
15 15
 interface MessageContract
16 16
 {
17 17
 
18
-	/**
19
-	 * @param WebSocket $socket
20
-	 * @param string $message
21
-	 * @return void
22
-	 */
23
-	public function onMessage(WebSocket $socket, string $message): void;
18
+    /**
19
+     * @param WebSocket $socket
20
+     * @param string $message
21
+     * @return void
22
+     */
23
+    public function onMessage(WebSocket $socket, string $message): void;
24 24
 
25 25
 }
Please login to merge, or discard this patch.
src/Contracts/WebSocketContract.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -15,24 +15,24 @@
 block discarded – undo
15 15
 interface WebSocketContract
16 16
 {
17 17
 
18
-	/**
19
-	 * @param WebSocket $socket
20
-	 * @return void
21
-	 */
22
-	public function onOpen(WebSocket $socket): void;
18
+    /**
19
+     * @param WebSocket $socket
20
+     * @return void
21
+     */
22
+    public function onOpen(WebSocket $socket): void;
23 23
 
24
-	/**
25
-	 * @param WebSocket $socket
26
-	 * @param int $closeStatus
27
-	 * @return void
28
-	 */
29
-	public function onClose(WebSocket $socket, int $closeStatus): void;
24
+    /**
25
+     * @param WebSocket $socket
26
+     * @param int $closeStatus
27
+     * @return void
28
+     */
29
+    public function onClose(WebSocket $socket, int $closeStatus): void;
30 30
 
31
-	/**
32
-	 * @param WebSocket $socket
33
-	 * @param WebSocketException $exception
34
-	 * @return void
35
-	 */
36
-	public function onError(WebSocket $socket, WebSocketException $exception): void;
31
+    /**
32
+     * @param WebSocket $socket
33
+     * @param WebSocketException $exception
34
+     * @return void
35
+     */
36
+    public function onError(WebSocket $socket, WebSocketException $exception): void;
37 37
 
38 38
 }
Please login to merge, or discard this patch.
src/WebSocket.php 2 patches
Indentation   +499 added lines, -499 removed lines patch added patch discarded remove patch
@@ -19,504 +19,504 @@
 block discarded – undo
19 19
 class WebSocket implements WscCommonsContract
20 20
 {
21 21
 
22
-	use WSClientTrait;
23
-
24
-	/**
25
-	 * @var callable|null
26
-	 */
27
-	public $onOpen = null;
28
-
29
-	/**
30
-	 * @var callable|null
31
-	 */
32
-	public $onClose = null;
33
-
34
-	/**
35
-	 * @var callable|null
36
-	 */
37
-	public $onError = null;
38
-
39
-	/**
40
-	 * @var callable|null
41
-	 */
42
-	public $onMessage = null;
43
-
44
-	/**
45
-	 * @var callable|null
46
-	 */
47
-	public $onWhile = null;
48
-
49
-	/**
50
-	 * App version
51
-	 *
52
-	 * @var string
53
-	 */
54
-	public const VERSION = 'v1.2.0';
55
-
56
-	/**
57
-	 * @var resource|bool
58
-	 */
59
-	private $socket;
60
-
61
-	/**
62
-	 * @var bool
63
-	 */
64
-	private bool $isConnected = false;
65
-
66
-	/**
67
-	 * @var bool
68
-	 */
69
-	private bool $isClosing = false;
70
-
71
-	/**
72
-	 * @var string
73
-	 */
74
-	private string $lastOpcode;
75
-
76
-	/**
77
-	 * @var float|int
78
-	 */
79
-	private float|int $closeStatus;
80
-
81
-	/**
82
-	 * @var string|null
83
-	 */
84
-	private ?string $hugePayload;
85
-
86
-	/**
87
-	 * @var array|int[]
88
-	 */
89
-	private static array $opcodes = [
90
-		CommonsContract::EVENT_TYPE_CONTINUATION => 0,
91
-		CommonsContract::EVENT_TYPE_TEXT => 1,
92
-		CommonsContract::EVENT_TYPE_BINARY => 2,
93
-		CommonsContract::EVENT_TYPE_CLOSE => 8,
94
-		CommonsContract::EVENT_TYPE_PING => 9,
95
-		CommonsContract::EVENT_TYPE_PONG => 10,
96
-	];
97
-
98
-	/**
99
-	 * @var WebSocketConfig
100
-	 */
101
-	protected WebSocketConfig $config;
102
-
103
-	/**
104
-	 * @var string
105
-	 */
106
-	protected string $socketUrl;
107
-
108
-	/**
109
-	 * Sets parameters for Web Socket Client intercommunication
110
-	 *
111
-	 * @param ?SocketClient $client leave it empty if you want to use default socket client
112
-	 */
113
-	public function __construct(?SocketClient $client = null)
114
-	{
115
-		if ($client instanceof SocketClient) {
116
-
117
-			$this->onOpen = function ($socket) use ($client) {
118
-				$client->onOpen($socket);
119
-			};
120
-
121
-			$this->onClose = function ($socket, int $closeStatus) use ($client) {
122
-				$client->onClose($socket, $closeStatus);
123
-			};
124
-
125
-			$this->onError = function ($socket, WebSocketException $exception) use ($client) {
126
-				$client->onError($socket, $exception);
127
-			};
128
-
129
-			$this->onMessage = function ($socket, string $message) use ($client) {
130
-				$client->onMessage($socket, $message);
131
-			};
132
-		}
133
-
134
-		$this->config = $config ?? new WebSocketConfig();
135
-	}
136
-
137
-	/**
138
-	 * @param string $socketUrl string that represents the URL of the Web Socket server. e.g. ws://localhost:1337 or wss://localhost:1337
139
-	 * @param ?WebSocketConfig $config The configuration for the Web Socket client
140
-	 */
141
-	public function connect(string $socketUrl, ?WebSocketConfig $config = null): void
142
-	{
143
-		try {
144
-			$this->config = $config ?? new WebSocketConfig();
145
-			$this->socketUrl = $socketUrl;
146
-			$urlParts = parse_url($this->socketUrl);
147
-
148
-			$this->config->setScheme($urlParts['scheme']);
149
-			$this->config->setHost($urlParts['host']);
150
-			$this->config->setUser($urlParts);
151
-			$this->config->setPassword($urlParts);
152
-			$this->config->setPort($urlParts);
153
-
154
-			$pathWithQuery = $this->getPathWithQuery($urlParts);
155
-			$hostUri = $this->getHostUri($this->config);
156
-
157
-			$context = $this->getStreamContext();
158
-			if ($this->config->hasProxy()) {
159
-				$this->socket = $this->proxy();
160
-			} else {
161
-				$this->socket = @stream_socket_client(
162
-					$hostUri . ':' . $this->config->getPort(),
163
-					$errno,
164
-					$errstr,
165
-					$this->config->getTimeout(),
166
-					STREAM_CLIENT_CONNECT,
167
-					$context
168
-				);
169
-			}
170
-
171
-			if ($this->socket === false) {
172
-				throw new ConnectionException(
173
-					"Could not open socket to \"{$this->config->getHost()}:{$this->config->getPort()}\": $errstr ($errno).",
174
-					CommonsContract::CLIENT_COULD_NOT_OPEN_SOCKET
175
-				);
176
-			}
177
-
178
-			stream_set_timeout($this->socket, $this->config->getTimeout());
179
-
180
-			$key = $this->generateKey();
181
-			$headers = [
182
-				'Host' => $this->config->getHost() . ':' . $this->config->getPort(),
183
-				'User-Agent' => 'Easy-Http/' . self::VERSION . ' (PHP/' . PHP_VERSION . ')',
184
-				'Connection' => 'Upgrade',
185
-				'Upgrade' => 'WebSocket',
186
-				'Sec-WebSocket-Key' => $key,
187
-				'Sec-Websocket-Version' => '13',
188
-			];
189
-
190
-			if ($this->config->getUser() || $this->config->getPassword()) {
191
-				$headers['authorization'] = 'Basic ' . base64_encode($this->config->getUser() . ':' . $this->config->getPassword()) . "\r\n";
192
-			}
193
-
194
-			if (!empty($this->config->getHeaders())) {
195
-				$headers = array_merge($headers, $this->config->getHeaders());
196
-			}
197
-
198
-			$header = $this->getHeaders($pathWithQuery, $headers);
199
-
200
-			$this->write($header);
201
-
202
-			$this->validateResponse($this->config, $pathWithQuery, $key);
203
-			$this->isConnected = true;
204
-			$this->onConnection();
205
-
206
-		} catch (\Exception $e) {
207
-			if (is_callable($this->onError) && $this->onError) {
208
-				call_user_func($this->onError, $this, new WebSocketException(
209
-					$e->getMessage(),
210
-					$e->getCode(),
211
-					$e
212
-				));
213
-			}
214
-		}
215
-	}
216
-
217
-	/**
218
-	 * Reconnect to the Web Socket server
219
-	 *
220
-	 * @throws \Exception
221
-	 * @return void
222
-	 */
223
-	public function reconnect(): void
224
-	{
225
-		if ($this->isConnected) {
226
-			$this->close();
227
-		}
228
-
229
-		$this->connect($this->socketUrl, $this->config);
230
-	}
231
-
232
-	/**
233
-	 * @return void
234
-	 * @throws WebSocketException|\Exception
235
-	 */
236
-	private function onConnection(): void
237
-	{
238
-		if (is_callable($this->onOpen) && $this->onOpen) {
239
-			call_user_func($this->onOpen, $this);
240
-		}
241
-
242
-		while ($this->isConnected()) {
243
-			if (is_callable($this->onWhile) && $this->onWhile) {
244
-				call_user_func($this->onWhile, $this);
245
-			}
246
-
247
-			if (is_string(($message = $this->receive()))) {
248
-				if (is_callable($this->onMessage) && $this->onMessage) {
249
-					call_user_func($this->onMessage, $this, $message);
250
-				}
251
-			}
252
-		}
253
-
254
-		if (is_callable($this->onClose) && $this->onClose) {
255
-			call_user_func($this->onClose, $this, $this->closeStatus);
256
-		}
257
-	}
258
-
259
-	/**
260
-	 * Init a proxy connection
261
-	 *
262
-	 * @return resource|false
263
-	 * @throws \InvalidArgumentException
264
-	 * @throws ConnectionException
265
-	 */
266
-	private function proxy()
267
-	{
268
-		$sock = @stream_socket_client(
269
-			WscCommonsContract::TCP_SCHEME . $this->config->getProxyIp() . ':' . $this->config->getProxyPort(),
270
-			$errno,
271
-			$errstr,
272
-			$this->config->getTimeout(),
273
-			STREAM_CLIENT_CONNECT,
274
-			$this->getStreamContext()
275
-		);
276
-
277
-		$write = "CONNECT {$this->config->getProxyIp()}:{$this->config->getProxyPort()} HTTP/1.1\r\n";
278
-		$auth = $this->config->getProxyAuth();
279
-
280
-		if ($auth !== NULL) {
281
-			$write .= "Proxy-Authorization: Basic {$auth}\r\n";
282
-		}
283
-
284
-		$write .= "\r\n";
285
-		fwrite($sock, $write);
286
-		$resp = fread($sock, 1024);
287
-
288
-		if (preg_match(self::PROXY_MATCH_RESP, $resp) === 1) {
289
-			return $sock;
290
-		}
291
-
292
-		throw new ConnectionException('Failed to connect to the host via proxy');
293
-	}
294
-
295
-	/**
296
-	 * @return mixed
297
-	 * @throws \InvalidArgumentException
298
-	 */
299
-	private function getStreamContext(): mixed
300
-	{
301
-		if ($this->config->getContext() !== null) {
302
-			// Suppress the error since we'll catch it below
303
-			if (@get_resource_type($this->config->getContext()) === 'stream-context') {
304
-				return $this->config->getContext();
305
-			}
306
-
307
-			throw new \InvalidArgumentException(
308
-				'Stream context is invalid',
309
-				CommonsContract::CLIENT_INVALID_STREAM_CONTEXT
310
-			);
311
-		}
312
-
313
-		return stream_context_create($this->config->getContextOptions());
314
-	}
315
-
316
-	/**
317
-	 * @param mixed $urlParts
318
-	 * @return string
319
-	 */
320
-	private function getPathWithQuery(mixed $urlParts): string
321
-	{
322
-		$path = $urlParts['path'] ?? '/';
323
-		$query = $urlParts['query'] ?? '';
324
-		$fragment = $urlParts['fragment'] ?? '';
325
-		$pathWithQuery = $path;
326
-
327
-		if (!empty($query)) {
328
-			$pathWithQuery .= '?' . $query;
329
-		}
330
-
331
-		if (!empty($fragment)) {
332
-			$pathWithQuery .= '#' . $fragment;
333
-		}
334
-
335
-		return $pathWithQuery;
336
-	}
337
-
338
-	/**
339
-	 * @param string $pathWithQuery
340
-	 * @param array $headers
341
-	 * @return string
342
-	 */
343
-	private function getHeaders(string $pathWithQuery, array $headers): string
344
-	{
345
-		return 'GET ' . $pathWithQuery . " HTTP/1.1\r\n"
346
-			. implode(
347
-				"\r\n",
348
-				array_map(
349
-					function ($key, $value) {
350
-						return "$key: $value";
351
-					},
352
-					array_keys($headers),
353
-					$headers
354
-				)
355
-			)
356
-			. "\r\n\r\n";
357
-	}
358
-
359
-	/**
360
-	 * @return string
361
-	 */
362
-	public function getLastOpcode(): string
363
-	{
364
-		return $this->lastOpcode;
365
-	}
366
-
367
-	/**
368
-	 * @return int
369
-	 */
370
-	public function getCloseStatus(): int
371
-	{
372
-		return $this->closeStatus;
373
-	}
374
-
375
-	/**
376
-	 * @return bool
377
-	 */
378
-	public function isConnected(): bool
379
-	{
380
-		return $this->isConnected;
381
-	}
382
-
383
-	/**
384
-	 * @param int $timeout
385
-	 * @param null $microSecs
386
-	 * @return void
387
-	 */
388
-	public function setTimeout(int $timeout, $microSecs = null): void
389
-	{
390
-		$this->config->setTimeout($timeout);
391
-		if ($this->socket && get_resource_type($this->socket) === 'stream') {
392
-			stream_set_timeout($this->socket, $timeout, $microSecs);
393
-		}
394
-	}
395
-
396
-	/**
397
-	 * Sends message to opened socket connection client->server
398
-	 *
399
-	 * @param $payload
400
-	 * @param string $opcode
401
-	 * @throws \Exception
402
-	 */
403
-	public function send($payload, string $opcode = CommonsContract::EVENT_TYPE_TEXT): void
404
-	{
405
-		if (!$this->isConnected) {
406
-			throw new \Exception(
407
-				"Can't send message. Connection is not established.",
408
-				CommonsContract::CLIENT_CONNECTION_NOT_ESTABLISHED
409
-			);
410
-		}
411
-
412
-		if (array_key_exists($opcode, self::$opcodes) === false) {
413
-			throw new BadOpcodeException(
414
-				sprintf("Bad opcode '%s'.  Try 'text' or 'binary'.", $opcode),
415
-				CommonsContract::CLIENT_BAD_OPCODE
416
-			);
417
-		}
418
-
419
-		$payloadLength = strlen($payload);
420
-		$fragmentCursor = 0;
421
-
422
-		while ($payloadLength > $fragmentCursor) {
423
-			$subPayload = substr($payload, $fragmentCursor, $this->config->getFragmentSize());
424
-			$fragmentCursor += $this->config->getFragmentSize();
425
-			$final = $payloadLength <= $fragmentCursor;
426
-			$this->sendFragment($final, $subPayload, $opcode, true);
427
-			$opcode = 'continuation';
428
-		}
429
-	}
430
-
431
-	/**
432
-	 * Receives message client<-server
433
-	 *
434
-	 * @return string|null
435
-	 * @throws \Exception
436
-	 */
437
-	public function receive(): string|null
438
-	{
439
-		if (!$this->isConnected && $this->isClosing === false) {
440
-			throw new WebSocketException(
441
-				"Your unexpectedly disconnected from the server",
442
-				CommonsContract::CLIENT_CONNECTION_NOT_ESTABLISHED
443
-			);
444
-		}
445
-
446
-		$this->hugePayload = '';
447
-
448
-		return $this->receiveFragment();
449
-	}
450
-
451
-	/**
452
-	 * Tell the socket to close.
453
-	 *
454
-	 * @param integer $status http://tools.ietf.org/html/rfc6455#section-7.4
455
-	 * @param string $message A closing message, max 125 bytes.
456
-	 * @return bool|null|string
457
-	 * @throws \Exception
458
-	 */
459
-	public function close(int $status = 1000, string $message = 'ttfn'): bool|null|string
460
-	{
461
-		$statusBin = sprintf('%016b', $status);
462
-		$statusStr = '';
463
-
464
-		foreach (str_split($statusBin, 8) as $binstr) {
465
-			$statusStr .= chr(bindec($binstr));
466
-		}
467
-
468
-		$this->send($statusStr . $message, CommonsContract::EVENT_TYPE_CLOSE);
469
-		$this->isClosing = true;
470
-
471
-		return $this->receive(); // Receiving a close frame will close the socket now.
472
-	}
473
-
474
-	/**
475
-	 * @return string
476
-	 */
477
-	public function getSocketUrl(): string
478
-	{
479
-		return $this->socketUrl;
480
-	}
481
-
482
-	/**
483
-	 * @param int $len
484
-	 * @return string|null
485
-	 * @throws ConnectionException
486
-	 */
487
-	protected function read(int $len): string|null
488
-	{
489
-		if ($this->socket && $this->isConnected()) {
490
-			return Middleware::stream_read($this->socket, $len);
491
-		}
492
-
493
-		return null;
494
-	}
495
-
496
-	/**
497
-	 * @param string $data
498
-	 * @throws ConnectionException
499
-	 */
500
-	protected function write(string $data): void
501
-	{
502
-		Middleware::stream_write($this->socket, $data);
503
-	}
504
-
505
-	/**
506
-	 * Helper to convert a binary to a string of '0' and '1'.
507
-	 *
508
-	 * @param string $string
509
-	 * @return string
510
-	 */
511
-	protected static function sprintB(string $string): string
512
-	{
513
-		$return = '';
514
-		$strLen = strlen($string);
515
-		for ($i = 0; $i < $strLen; $i++) {
516
-			$return .= sprintf('%08b', ord($string[$i]));
517
-		}
518
-
519
-		return $return;
520
-	}
22
+    use WSClientTrait;
23
+
24
+    /**
25
+     * @var callable|null
26
+     */
27
+    public $onOpen = null;
28
+
29
+    /**
30
+     * @var callable|null
31
+     */
32
+    public $onClose = null;
33
+
34
+    /**
35
+     * @var callable|null
36
+     */
37
+    public $onError = null;
38
+
39
+    /**
40
+     * @var callable|null
41
+     */
42
+    public $onMessage = null;
43
+
44
+    /**
45
+     * @var callable|null
46
+     */
47
+    public $onWhile = null;
48
+
49
+    /**
50
+     * App version
51
+     *
52
+     * @var string
53
+     */
54
+    public const VERSION = 'v1.2.0';
55
+
56
+    /**
57
+     * @var resource|bool
58
+     */
59
+    private $socket;
60
+
61
+    /**
62
+     * @var bool
63
+     */
64
+    private bool $isConnected = false;
65
+
66
+    /**
67
+     * @var bool
68
+     */
69
+    private bool $isClosing = false;
70
+
71
+    /**
72
+     * @var string
73
+     */
74
+    private string $lastOpcode;
75
+
76
+    /**
77
+     * @var float|int
78
+     */
79
+    private float|int $closeStatus;
80
+
81
+    /**
82
+     * @var string|null
83
+     */
84
+    private ?string $hugePayload;
85
+
86
+    /**
87
+     * @var array|int[]
88
+     */
89
+    private static array $opcodes = [
90
+        CommonsContract::EVENT_TYPE_CONTINUATION => 0,
91
+        CommonsContract::EVENT_TYPE_TEXT => 1,
92
+        CommonsContract::EVENT_TYPE_BINARY => 2,
93
+        CommonsContract::EVENT_TYPE_CLOSE => 8,
94
+        CommonsContract::EVENT_TYPE_PING => 9,
95
+        CommonsContract::EVENT_TYPE_PONG => 10,
96
+    ];
97
+
98
+    /**
99
+     * @var WebSocketConfig
100
+     */
101
+    protected WebSocketConfig $config;
102
+
103
+    /**
104
+     * @var string
105
+     */
106
+    protected string $socketUrl;
107
+
108
+    /**
109
+     * Sets parameters for Web Socket Client intercommunication
110
+     *
111
+     * @param ?SocketClient $client leave it empty if you want to use default socket client
112
+     */
113
+    public function __construct(?SocketClient $client = null)
114
+    {
115
+        if ($client instanceof SocketClient) {
116
+
117
+            $this->onOpen = function ($socket) use ($client) {
118
+                $client->onOpen($socket);
119
+            };
120
+
121
+            $this->onClose = function ($socket, int $closeStatus) use ($client) {
122
+                $client->onClose($socket, $closeStatus);
123
+            };
124
+
125
+            $this->onError = function ($socket, WebSocketException $exception) use ($client) {
126
+                $client->onError($socket, $exception);
127
+            };
128
+
129
+            $this->onMessage = function ($socket, string $message) use ($client) {
130
+                $client->onMessage($socket, $message);
131
+            };
132
+        }
133
+
134
+        $this->config = $config ?? new WebSocketConfig();
135
+    }
136
+
137
+    /**
138
+     * @param string $socketUrl string that represents the URL of the Web Socket server. e.g. ws://localhost:1337 or wss://localhost:1337
139
+     * @param ?WebSocketConfig $config The configuration for the Web Socket client
140
+     */
141
+    public function connect(string $socketUrl, ?WebSocketConfig $config = null): void
142
+    {
143
+        try {
144
+            $this->config = $config ?? new WebSocketConfig();
145
+            $this->socketUrl = $socketUrl;
146
+            $urlParts = parse_url($this->socketUrl);
147
+
148
+            $this->config->setScheme($urlParts['scheme']);
149
+            $this->config->setHost($urlParts['host']);
150
+            $this->config->setUser($urlParts);
151
+            $this->config->setPassword($urlParts);
152
+            $this->config->setPort($urlParts);
153
+
154
+            $pathWithQuery = $this->getPathWithQuery($urlParts);
155
+            $hostUri = $this->getHostUri($this->config);
156
+
157
+            $context = $this->getStreamContext();
158
+            if ($this->config->hasProxy()) {
159
+                $this->socket = $this->proxy();
160
+            } else {
161
+                $this->socket = @stream_socket_client(
162
+                    $hostUri . ':' . $this->config->getPort(),
163
+                    $errno,
164
+                    $errstr,
165
+                    $this->config->getTimeout(),
166
+                    STREAM_CLIENT_CONNECT,
167
+                    $context
168
+                );
169
+            }
170
+
171
+            if ($this->socket === false) {
172
+                throw new ConnectionException(
173
+                    "Could not open socket to \"{$this->config->getHost()}:{$this->config->getPort()}\": $errstr ($errno).",
174
+                    CommonsContract::CLIENT_COULD_NOT_OPEN_SOCKET
175
+                );
176
+            }
177
+
178
+            stream_set_timeout($this->socket, $this->config->getTimeout());
179
+
180
+            $key = $this->generateKey();
181
+            $headers = [
182
+                'Host' => $this->config->getHost() . ':' . $this->config->getPort(),
183
+                'User-Agent' => 'Easy-Http/' . self::VERSION . ' (PHP/' . PHP_VERSION . ')',
184
+                'Connection' => 'Upgrade',
185
+                'Upgrade' => 'WebSocket',
186
+                'Sec-WebSocket-Key' => $key,
187
+                'Sec-Websocket-Version' => '13',
188
+            ];
189
+
190
+            if ($this->config->getUser() || $this->config->getPassword()) {
191
+                $headers['authorization'] = 'Basic ' . base64_encode($this->config->getUser() . ':' . $this->config->getPassword()) . "\r\n";
192
+            }
193
+
194
+            if (!empty($this->config->getHeaders())) {
195
+                $headers = array_merge($headers, $this->config->getHeaders());
196
+            }
197
+
198
+            $header = $this->getHeaders($pathWithQuery, $headers);
199
+
200
+            $this->write($header);
201
+
202
+            $this->validateResponse($this->config, $pathWithQuery, $key);
203
+            $this->isConnected = true;
204
+            $this->onConnection();
205
+
206
+        } catch (\Exception $e) {
207
+            if (is_callable($this->onError) && $this->onError) {
208
+                call_user_func($this->onError, $this, new WebSocketException(
209
+                    $e->getMessage(),
210
+                    $e->getCode(),
211
+                    $e
212
+                ));
213
+            }
214
+        }
215
+    }
216
+
217
+    /**
218
+     * Reconnect to the Web Socket server
219
+     *
220
+     * @throws \Exception
221
+     * @return void
222
+     */
223
+    public function reconnect(): void
224
+    {
225
+        if ($this->isConnected) {
226
+            $this->close();
227
+        }
228
+
229
+        $this->connect($this->socketUrl, $this->config);
230
+    }
231
+
232
+    /**
233
+     * @return void
234
+     * @throws WebSocketException|\Exception
235
+     */
236
+    private function onConnection(): void
237
+    {
238
+        if (is_callable($this->onOpen) && $this->onOpen) {
239
+            call_user_func($this->onOpen, $this);
240
+        }
241
+
242
+        while ($this->isConnected()) {
243
+            if (is_callable($this->onWhile) && $this->onWhile) {
244
+                call_user_func($this->onWhile, $this);
245
+            }
246
+
247
+            if (is_string(($message = $this->receive()))) {
248
+                if (is_callable($this->onMessage) && $this->onMessage) {
249
+                    call_user_func($this->onMessage, $this, $message);
250
+                }
251
+            }
252
+        }
253
+
254
+        if (is_callable($this->onClose) && $this->onClose) {
255
+            call_user_func($this->onClose, $this, $this->closeStatus);
256
+        }
257
+    }
258
+
259
+    /**
260
+     * Init a proxy connection
261
+     *
262
+     * @return resource|false
263
+     * @throws \InvalidArgumentException
264
+     * @throws ConnectionException
265
+     */
266
+    private function proxy()
267
+    {
268
+        $sock = @stream_socket_client(
269
+            WscCommonsContract::TCP_SCHEME . $this->config->getProxyIp() . ':' . $this->config->getProxyPort(),
270
+            $errno,
271
+            $errstr,
272
+            $this->config->getTimeout(),
273
+            STREAM_CLIENT_CONNECT,
274
+            $this->getStreamContext()
275
+        );
276
+
277
+        $write = "CONNECT {$this->config->getProxyIp()}:{$this->config->getProxyPort()} HTTP/1.1\r\n";
278
+        $auth = $this->config->getProxyAuth();
279
+
280
+        if ($auth !== NULL) {
281
+            $write .= "Proxy-Authorization: Basic {$auth}\r\n";
282
+        }
283
+
284
+        $write .= "\r\n";
285
+        fwrite($sock, $write);
286
+        $resp = fread($sock, 1024);
287
+
288
+        if (preg_match(self::PROXY_MATCH_RESP, $resp) === 1) {
289
+            return $sock;
290
+        }
291
+
292
+        throw new ConnectionException('Failed to connect to the host via proxy');
293
+    }
294
+
295
+    /**
296
+     * @return mixed
297
+     * @throws \InvalidArgumentException
298
+     */
299
+    private function getStreamContext(): mixed
300
+    {
301
+        if ($this->config->getContext() !== null) {
302
+            // Suppress the error since we'll catch it below
303
+            if (@get_resource_type($this->config->getContext()) === 'stream-context') {
304
+                return $this->config->getContext();
305
+            }
306
+
307
+            throw new \InvalidArgumentException(
308
+                'Stream context is invalid',
309
+                CommonsContract::CLIENT_INVALID_STREAM_CONTEXT
310
+            );
311
+        }
312
+
313
+        return stream_context_create($this->config->getContextOptions());
314
+    }
315
+
316
+    /**
317
+     * @param mixed $urlParts
318
+     * @return string
319
+     */
320
+    private function getPathWithQuery(mixed $urlParts): string
321
+    {
322
+        $path = $urlParts['path'] ?? '/';
323
+        $query = $urlParts['query'] ?? '';
324
+        $fragment = $urlParts['fragment'] ?? '';
325
+        $pathWithQuery = $path;
326
+
327
+        if (!empty($query)) {
328
+            $pathWithQuery .= '?' . $query;
329
+        }
330
+
331
+        if (!empty($fragment)) {
332
+            $pathWithQuery .= '#' . $fragment;
333
+        }
334
+
335
+        return $pathWithQuery;
336
+    }
337
+
338
+    /**
339
+     * @param string $pathWithQuery
340
+     * @param array $headers
341
+     * @return string
342
+     */
343
+    private function getHeaders(string $pathWithQuery, array $headers): string
344
+    {
345
+        return 'GET ' . $pathWithQuery . " HTTP/1.1\r\n"
346
+            . implode(
347
+                "\r\n",
348
+                array_map(
349
+                    function ($key, $value) {
350
+                        return "$key: $value";
351
+                    },
352
+                    array_keys($headers),
353
+                    $headers
354
+                )
355
+            )
356
+            . "\r\n\r\n";
357
+    }
358
+
359
+    /**
360
+     * @return string
361
+     */
362
+    public function getLastOpcode(): string
363
+    {
364
+        return $this->lastOpcode;
365
+    }
366
+
367
+    /**
368
+     * @return int
369
+     */
370
+    public function getCloseStatus(): int
371
+    {
372
+        return $this->closeStatus;
373
+    }
374
+
375
+    /**
376
+     * @return bool
377
+     */
378
+    public function isConnected(): bool
379
+    {
380
+        return $this->isConnected;
381
+    }
382
+
383
+    /**
384
+     * @param int $timeout
385
+     * @param null $microSecs
386
+     * @return void
387
+     */
388
+    public function setTimeout(int $timeout, $microSecs = null): void
389
+    {
390
+        $this->config->setTimeout($timeout);
391
+        if ($this->socket && get_resource_type($this->socket) === 'stream') {
392
+            stream_set_timeout($this->socket, $timeout, $microSecs);
393
+        }
394
+    }
395
+
396
+    /**
397
+     * Sends message to opened socket connection client->server
398
+     *
399
+     * @param $payload
400
+     * @param string $opcode
401
+     * @throws \Exception
402
+     */
403
+    public function send($payload, string $opcode = CommonsContract::EVENT_TYPE_TEXT): void
404
+    {
405
+        if (!$this->isConnected) {
406
+            throw new \Exception(
407
+                "Can't send message. Connection is not established.",
408
+                CommonsContract::CLIENT_CONNECTION_NOT_ESTABLISHED
409
+            );
410
+        }
411
+
412
+        if (array_key_exists($opcode, self::$opcodes) === false) {
413
+            throw new BadOpcodeException(
414
+                sprintf("Bad opcode '%s'.  Try 'text' or 'binary'.", $opcode),
415
+                CommonsContract::CLIENT_BAD_OPCODE
416
+            );
417
+        }
418
+
419
+        $payloadLength = strlen($payload);
420
+        $fragmentCursor = 0;
421
+
422
+        while ($payloadLength > $fragmentCursor) {
423
+            $subPayload = substr($payload, $fragmentCursor, $this->config->getFragmentSize());
424
+            $fragmentCursor += $this->config->getFragmentSize();
425
+            $final = $payloadLength <= $fragmentCursor;
426
+            $this->sendFragment($final, $subPayload, $opcode, true);
427
+            $opcode = 'continuation';
428
+        }
429
+    }
430
+
431
+    /**
432
+     * Receives message client<-server
433
+     *
434
+     * @return string|null
435
+     * @throws \Exception
436
+     */
437
+    public function receive(): string|null
438
+    {
439
+        if (!$this->isConnected && $this->isClosing === false) {
440
+            throw new WebSocketException(
441
+                "Your unexpectedly disconnected from the server",
442
+                CommonsContract::CLIENT_CONNECTION_NOT_ESTABLISHED
443
+            );
444
+        }
445
+
446
+        $this->hugePayload = '';
447
+
448
+        return $this->receiveFragment();
449
+    }
450
+
451
+    /**
452
+     * Tell the socket to close.
453
+     *
454
+     * @param integer $status http://tools.ietf.org/html/rfc6455#section-7.4
455
+     * @param string $message A closing message, max 125 bytes.
456
+     * @return bool|null|string
457
+     * @throws \Exception
458
+     */
459
+    public function close(int $status = 1000, string $message = 'ttfn'): bool|null|string
460
+    {
461
+        $statusBin = sprintf('%016b', $status);
462
+        $statusStr = '';
463
+
464
+        foreach (str_split($statusBin, 8) as $binstr) {
465
+            $statusStr .= chr(bindec($binstr));
466
+        }
467
+
468
+        $this->send($statusStr . $message, CommonsContract::EVENT_TYPE_CLOSE);
469
+        $this->isClosing = true;
470
+
471
+        return $this->receive(); // Receiving a close frame will close the socket now.
472
+    }
473
+
474
+    /**
475
+     * @return string
476
+     */
477
+    public function getSocketUrl(): string
478
+    {
479
+        return $this->socketUrl;
480
+    }
481
+
482
+    /**
483
+     * @param int $len
484
+     * @return string|null
485
+     * @throws ConnectionException
486
+     */
487
+    protected function read(int $len): string|null
488
+    {
489
+        if ($this->socket && $this->isConnected()) {
490
+            return Middleware::stream_read($this->socket, $len);
491
+        }
492
+
493
+        return null;
494
+    }
495
+
496
+    /**
497
+     * @param string $data
498
+     * @throws ConnectionException
499
+     */
500
+    protected function write(string $data): void
501
+    {
502
+        Middleware::stream_write($this->socket, $data);
503
+    }
504
+
505
+    /**
506
+     * Helper to convert a binary to a string of '0' and '1'.
507
+     *
508
+     * @param string $string
509
+     * @return string
510
+     */
511
+    protected static function sprintB(string $string): string
512
+    {
513
+        $return = '';
514
+        $strLen = strlen($string);
515
+        for ($i = 0; $i < $strLen; $i++) {
516
+            $return .= sprintf('%08b', ord($string[$i]));
517
+        }
518
+
519
+        return $return;
520
+    }
521 521
 
522 522
 }
523 523
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -114,19 +114,19 @@  discard block
 block discarded – undo
114 114
 	{
115 115
 		if ($client instanceof SocketClient) {
116 116
 
117
-			$this->onOpen = function ($socket) use ($client) {
117
+			$this->onOpen = function($socket) use ($client) {
118 118
 				$client->onOpen($socket);
119 119
 			};
120 120
 
121
-			$this->onClose = function ($socket, int $closeStatus) use ($client) {
121
+			$this->onClose = function($socket, int $closeStatus) use ($client) {
122 122
 				$client->onClose($socket, $closeStatus);
123 123
 			};
124 124
 
125
-			$this->onError = function ($socket, WebSocketException $exception) use ($client) {
125
+			$this->onError = function($socket, WebSocketException $exception) use ($client) {
126 126
 				$client->onError($socket, $exception);
127 127
 			};
128 128
 
129
-			$this->onMessage = function ($socket, string $message) use ($client) {
129
+			$this->onMessage = function($socket, string $message) use ($client) {
130 130
 				$client->onMessage($socket, $message);
131 131
 			};
132 132
 		}
@@ -346,7 +346,7 @@  discard block
 block discarded – undo
346 346
 			. implode(
347 347
 				"\r\n",
348 348
 				array_map(
349
-					function ($key, $value) {
349
+					function($key, $value) {
350 350
 						return "$key: $value";
351 351
 					},
352 352
 					array_keys($headers),
Please login to merge, or discard this patch.
src/Middleware.php 1 patch
Indentation   +183 added lines, -183 removed lines patch added patch discarded remove patch
@@ -17,193 +17,193 @@
 block discarded – undo
17 17
 class Middleware
18 18
 {
19 19
 
20
-	/**
21
-	 * Create curl handler.
22
-	 *
23
-	 * @param ?string $method
24
-	 * @param string $uri
25
-	 * @param array|HttpOptions $options
26
-	 *
27
-	 * @return \CurlHandle|false
28
-	 */
29
-	public static function create_curl_handler(?string $method, string $uri, array|HttpOptions $options = []): \CurlHandle|false
30
-	{
31
-		$handler = curl_init();
32
-		if (is_resource($handler) || !$handler) {
33
-			return false;
34
-		}
35
-
36
-		if (gettype($options) === 'array') {
37
-			$options = new HttpOptions($options);
38
-		}
39
-
40
-		if (count($options->getQuery()) > 0) {
41
-			if (!str_contains($uri, '?')) {
42
-				$uri .= '?';
43
-			}
44
-			$uri .= $options->getQueryString();
45
-		}
46
-
47
-		curl_setopt($handler, CURLOPT_URL, $uri);
48
-
49
-		self::set_curl_options($method, $handler, $options);
50
-
51
-		return $handler;
52
-	}
53
-
54
-	/**
55
-	 * Setup curl options based on the given method and our options.
56
-	 *
57
-	 * @param \CurlHandle $cHandler
58
-	 * @param ?string $method
59
-	 * @param HttpOptions $options
60
-	 *
61
-	 * @return void
62
-	 */
63
-	public static function set_curl_options(?string $method, \CurlHandle $cHandler, HttpOptions $options): void
64
-	{
65
-		curl_setopt($cHandler, CURLOPT_HEADER, true);
66
-		curl_setopt($cHandler, CURLOPT_CUSTOMREQUEST, $method ?? 'GET');
67
-
68
-		# Fetch the header
69
-		$fetchedHeaders = [];
70
-		foreach ($options->getHeaders() as $header => $value) {
71
-			$fetchedHeaders[] = $header . ': ' . $value;
72
-		}
73
-
74
-		# Set headers
75
-		curl_setopt($cHandler, CURLOPT_HTTPHEADER, $fetchedHeaders ?? []);
76
-
77
-		# Add body if we have one.
78
-		if ($options->getBody()) {
79
-			curl_setopt($cHandler, CURLOPT_CUSTOMREQUEST, $method ?? 'POST');
80
-			curl_setopt($cHandler, CURLOPT_POSTFIELDS, $options->getBody());
81
-			curl_setopt($cHandler, CURLOPT_POST, true);
82
-		}
83
-
84
-		# Check for a proxy
85
-		if ($options->getProxy() != null) {
86
-			curl_setopt($cHandler, CURLOPT_PROXY, $options->getProxy()->getHost());
87
-			curl_setopt($cHandler, CURLOPT_PROXYUSERPWD, $options->getProxy()->getAuth());
88
-			if ($options->getProxy()->type !== null) {
89
-				curl_setopt($cHandler, CURLOPT_PROXYTYPE, $options->getProxy()->type);
90
-			}
91
-		}
92
-
93
-		curl_setopt($cHandler, CURLOPT_RETURNTRANSFER, true);
94
-		curl_setopt($cHandler, CURLOPT_FOLLOWLOCATION, true);
95
-
96
-		# Add and override the custom curl options.
97
-		foreach ($options->getCurlOptions() as $option => $value) {
98
-			curl_setopt($cHandler, $option, $value);
99
-		}
100
-
101
-		# if we have a timeout, set it.
102
-		curl_setopt($cHandler, CURLOPT_TIMEOUT, $options->getTimeout());
103
-
104
-		# If self-signed certs are allowed, set it.
105
-		if ((bool)getenv('HAS_SELF_SIGNED_CERT') === true) {
106
-			curl_setopt($cHandler, CURLOPT_SSL_VERIFYPEER, false);
107
-			curl_setopt($cHandler, CURLOPT_SSL_VERIFYHOST, false);
108
-		}
109
-
110
-		(new Middleware())->handle_media($cHandler, $options);
111
-	}
112
-
113
-	/**
114
-	 * Handle the media
115
-	 *
116
-	 * @param \CurlHandle $handler
117
-	 * @param HttpOptions $options
118
-	 * @return void
119
-	 */
120
-	private function handle_media(\CurlHandle $handler, HttpOptions $options): void
121
-	{
122
-		if (count($options->getMultipart()) > 0) {
123
-			curl_setopt($handler, CURLOPT_POST, true);
124
-			curl_setopt($handler, CURLOPT_CUSTOMREQUEST, 'POST');
125
-
126
-			$form_data = new FormData();
127
-			foreach ($options->getMultipart() as $key => $value) {
128
-				$form_data->addFile($key, $value);
129
-			}
130
-
131
-			$headers = [];
132
-			foreach ($options->getHeaders() as $header => $value) {
133
-				if (Toolkit::insensitiveString($header, 'content-type')) continue;
134
-				$headers[] = $header . ': ' . $value;
135
-			}
136
-			$headers[] = 'Content-Type: multipart/form-data';
137
-
138
-			curl_setopt($handler, CURLOPT_HTTPHEADER, $headers);
139
-			curl_setopt($handler, CURLOPT_POSTFIELDS, $form_data->getFiles());
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * @param mixed $socket
145
-	 * @param int $len
146
-	 * @return string|null
147
-	 * @throws ConnectionException
148
-	 */
149
-	public static function stream_read(mixed $socket, int $len): string|null
150
-	{
151
-		if (!is_resource($socket)) {
152
-			throw new ConnectionException(sprintf(
153
-				'%s is not a valid resource. Datatype: %s', $socket, gettype($socket)
154
-			));
155
-		}
156
-
157
-		$data = '';
158
-		while (($dataLen = strlen($data)) < $len) {
159
-			$buff = fread($socket, $len - $dataLen);
160
-
161
-			if ($buff === false) {
20
+    /**
21
+     * Create curl handler.
22
+     *
23
+     * @param ?string $method
24
+     * @param string $uri
25
+     * @param array|HttpOptions $options
26
+     *
27
+     * @return \CurlHandle|false
28
+     */
29
+    public static function create_curl_handler(?string $method, string $uri, array|HttpOptions $options = []): \CurlHandle|false
30
+    {
31
+        $handler = curl_init();
32
+        if (is_resource($handler) || !$handler) {
33
+            return false;
34
+        }
35
+
36
+        if (gettype($options) === 'array') {
37
+            $options = new HttpOptions($options);
38
+        }
39
+
40
+        if (count($options->getQuery()) > 0) {
41
+            if (!str_contains($uri, '?')) {
42
+                $uri .= '?';
43
+            }
44
+            $uri .= $options->getQueryString();
45
+        }
46
+
47
+        curl_setopt($handler, CURLOPT_URL, $uri);
48
+
49
+        self::set_curl_options($method, $handler, $options);
50
+
51
+        return $handler;
52
+    }
53
+
54
+    /**
55
+     * Setup curl options based on the given method and our options.
56
+     *
57
+     * @param \CurlHandle $cHandler
58
+     * @param ?string $method
59
+     * @param HttpOptions $options
60
+     *
61
+     * @return void
62
+     */
63
+    public static function set_curl_options(?string $method, \CurlHandle $cHandler, HttpOptions $options): void
64
+    {
65
+        curl_setopt($cHandler, CURLOPT_HEADER, true);
66
+        curl_setopt($cHandler, CURLOPT_CUSTOMREQUEST, $method ?? 'GET');
67
+
68
+        # Fetch the header
69
+        $fetchedHeaders = [];
70
+        foreach ($options->getHeaders() as $header => $value) {
71
+            $fetchedHeaders[] = $header . ': ' . $value;
72
+        }
73
+
74
+        # Set headers
75
+        curl_setopt($cHandler, CURLOPT_HTTPHEADER, $fetchedHeaders ?? []);
76
+
77
+        # Add body if we have one.
78
+        if ($options->getBody()) {
79
+            curl_setopt($cHandler, CURLOPT_CUSTOMREQUEST, $method ?? 'POST');
80
+            curl_setopt($cHandler, CURLOPT_POSTFIELDS, $options->getBody());
81
+            curl_setopt($cHandler, CURLOPT_POST, true);
82
+        }
83
+
84
+        # Check for a proxy
85
+        if ($options->getProxy() != null) {
86
+            curl_setopt($cHandler, CURLOPT_PROXY, $options->getProxy()->getHost());
87
+            curl_setopt($cHandler, CURLOPT_PROXYUSERPWD, $options->getProxy()->getAuth());
88
+            if ($options->getProxy()->type !== null) {
89
+                curl_setopt($cHandler, CURLOPT_PROXYTYPE, $options->getProxy()->type);
90
+            }
91
+        }
92
+
93
+        curl_setopt($cHandler, CURLOPT_RETURNTRANSFER, true);
94
+        curl_setopt($cHandler, CURLOPT_FOLLOWLOCATION, true);
95
+
96
+        # Add and override the custom curl options.
97
+        foreach ($options->getCurlOptions() as $option => $value) {
98
+            curl_setopt($cHandler, $option, $value);
99
+        }
100
+
101
+        # if we have a timeout, set it.
102
+        curl_setopt($cHandler, CURLOPT_TIMEOUT, $options->getTimeout());
103
+
104
+        # If self-signed certs are allowed, set it.
105
+        if ((bool)getenv('HAS_SELF_SIGNED_CERT') === true) {
106
+            curl_setopt($cHandler, CURLOPT_SSL_VERIFYPEER, false);
107
+            curl_setopt($cHandler, CURLOPT_SSL_VERIFYHOST, false);
108
+        }
109
+
110
+        (new Middleware())->handle_media($cHandler, $options);
111
+    }
112
+
113
+    /**
114
+     * Handle the media
115
+     *
116
+     * @param \CurlHandle $handler
117
+     * @param HttpOptions $options
118
+     * @return void
119
+     */
120
+    private function handle_media(\CurlHandle $handler, HttpOptions $options): void
121
+    {
122
+        if (count($options->getMultipart()) > 0) {
123
+            curl_setopt($handler, CURLOPT_POST, true);
124
+            curl_setopt($handler, CURLOPT_CUSTOMREQUEST, 'POST');
125
+
126
+            $form_data = new FormData();
127
+            foreach ($options->getMultipart() as $key => $value) {
128
+                $form_data->addFile($key, $value);
129
+            }
130
+
131
+            $headers = [];
132
+            foreach ($options->getHeaders() as $header => $value) {
133
+                if (Toolkit::insensitiveString($header, 'content-type')) continue;
134
+                $headers[] = $header . ': ' . $value;
135
+            }
136
+            $headers[] = 'Content-Type: multipart/form-data';
137
+
138
+            curl_setopt($handler, CURLOPT_HTTPHEADER, $headers);
139
+            curl_setopt($handler, CURLOPT_POSTFIELDS, $form_data->getFiles());
140
+        }
141
+    }
142
+
143
+    /**
144
+     * @param mixed $socket
145
+     * @param int $len
146
+     * @return string|null
147
+     * @throws ConnectionException
148
+     */
149
+    public static function stream_read(mixed $socket, int $len): string|null
150
+    {
151
+        if (!is_resource($socket)) {
152
+            throw new ConnectionException(sprintf(
153
+                '%s is not a valid resource. Datatype: %s', $socket, gettype($socket)
154
+            ));
155
+        }
156
+
157
+        $data = '';
158
+        while (($dataLen = strlen($data)) < $len) {
159
+            $buff = fread($socket, $len - $dataLen);
160
+
161
+            if ($buff === false) {
162 162
 //				$metadata = stream_get_meta_data($socket);
163 163
 //				throw new ConnectionException(
164 164
 //					sprintf('Broken frame, read %s of stated %s bytes.  Stream state: %s', strlen($data), $len, json_encode($metadata)),
165 165
 //					CommonsContract::CLIENT_BROKEN_FRAME
166 166
 //				);
167
-				return null;
168
-			}
169
-
170
-			if ($buff === '') {
171
-				$metadata = stream_get_meta_data($socket);
172
-				throw new ConnectionException(
173
-					sprintf('Empty read; connection dead?  Stream state: %s', json_encode($metadata)),
174
-					CommonsContract::CLIENT_EMPTY_READ
175
-				);
176
-			}
177
-			$data .= $buff;
178
-		}
179
-
180
-		return $data;
181
-	}
182
-
183
-	/**
184
-	 * @param mixed $socket
185
-	 * @param string $data
186
-	 * @return bool
187
-	 * @throws ConnectionException
188
-	 */
189
-	public static function stream_write(mixed $socket, string $data): bool
190
-	{
191
-		if (!is_resource($socket)) {
192
-			throw new ConnectionException(sprintf(
193
-				'%s is not a valid resource. Datatype: %s', $socket, gettype($socket)
194
-			));
195
-		}
196
-
197
-		$written = fwrite($socket, $data);
198
-
199
-		if ($written < strlen($data)) {
200
-			throw new ConnectionException(
201
-				sprintf('Could only write %s out of %s bytes.', $written, strlen($data)),
202
-				CommonsContract::CLIENT_COULD_ONLY_WRITE_LESS
203
-			);
204
-		}
205
-
206
-		return true;
207
-	}
167
+                return null;
168
+            }
169
+
170
+            if ($buff === '') {
171
+                $metadata = stream_get_meta_data($socket);
172
+                throw new ConnectionException(
173
+                    sprintf('Empty read; connection dead?  Stream state: %s', json_encode($metadata)),
174
+                    CommonsContract::CLIENT_EMPTY_READ
175
+                );
176
+            }
177
+            $data .= $buff;
178
+        }
179
+
180
+        return $data;
181
+    }
182
+
183
+    /**
184
+     * @param mixed $socket
185
+     * @param string $data
186
+     * @return bool
187
+     * @throws ConnectionException
188
+     */
189
+    public static function stream_write(mixed $socket, string $data): bool
190
+    {
191
+        if (!is_resource($socket)) {
192
+            throw new ConnectionException(sprintf(
193
+                '%s is not a valid resource. Datatype: %s', $socket, gettype($socket)
194
+            ));
195
+        }
196
+
197
+        $written = fwrite($socket, $data);
198
+
199
+        if ($written < strlen($data)) {
200
+            throw new ConnectionException(
201
+                sprintf('Could only write %s out of %s bytes.', $written, strlen($data)),
202
+                CommonsContract::CLIENT_COULD_ONLY_WRITE_LESS
203
+            );
204
+        }
205
+
206
+        return true;
207
+    }
208 208
 
209 209
 }
210 210
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/WSClientTrait.php 2 patches
Indentation   +242 added lines, -242 removed lines patch added patch discarded remove patch
@@ -17,247 +17,247 @@
 block discarded – undo
17 17
 trait WSClientTrait
18 18
 {
19 19
 
20
-	/**
21
-	 * Validates whether server sent valid upgrade response
22
-	 *
23
-	 * @param WebSocketConfig $config
24
-	 * @param string $pathWithQuery
25
-	 * @param string $key
26
-	 * @throws ConnectionException
27
-	 */
28
-	private function validateResponse(WebSocketConfig $config, string $pathWithQuery, string $key): void
29
-	{
30
-		$response = stream_get_line($this->socket, self::DEFAULT_RESPONSE_HEADER, "\r\n\r\n");
31
-		if (!preg_match(self::SEC_WEBSOCKET_ACCEPT_PTTRN, $response, $matches)) {
32
-			$address = $config->getScheme() . '://' . $config->getHost() . ':' . $config->getPort() . $pathWithQuery;
33
-			throw new ConnectionException(
34
-				"Connection to '{$address}' failed: Server sent invalid upgrade response:\n"
35
-				. $response, CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
36
-			);
37
-		}
38
-
39
-		$keyAccept = trim($matches[1]);
40
-		$expectedResponse = base64_encode(pack('H*', sha1($key . self::SERVER_KEY_ACCEPT)));
41
-		if ($keyAccept !== $expectedResponse) {
42
-			throw new ConnectionException(
43
-				'Server sent bad upgrade response.',
44
-				CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
45
-			);
46
-		}
47
-	}
48
-
49
-	/**
50
-	 *  Gets host uri based on protocol
51
-	 *
52
-	 * @param WebSocketConfig $config
53
-	 * @return string
54
-	 * @throws BadUriException
55
-	 */
56
-	private function getHostUri(WebSocketConfig $config): string
57
-	{
58
-		if (in_array($config->getScheme(), ['ws', 'wss'], true) === false) {
59
-			throw new BadUriException(
60
-				"Url should have scheme ws or wss, not '{$config->getScheme()}' from URI '$this->socketUrl' .",
61
-				CommonsContract::CLIENT_INCORRECT_SCHEME
62
-			);
63
-		}
64
-
65
-		return ($config->getScheme() === 'wss' ? 'ssl' : 'tcp') . '://' . $config->getHost();
66
-	}
67
-
68
-	/**
69
-	 * @param string $data
70
-	 * @return float|int
71
-	 * @throws ConnectionException
72
-	 */
73
-	private function getPayloadLength(string $data): float|int
74
-	{
75
-		$payloadLength = (int)ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
76
-		if ($payloadLength > self::MASK_125) {
77
-			if ($payloadLength === self::MASK_126) {
78
-				$data = $this->read(2); // 126: Payload is a 16-bit unsigned int
79
-			} else {
80
-				$data = $this->read(8); // 127: Payload is a 64-bit unsigned int
81
-			}
82
-			$payloadLength = bindec(self::sprintB($data));
83
-		}
84
-
85
-		return $payloadLength;
86
-	}
87
-
88
-	/**
89
-	 * @param string $data
90
-	 * @param int $payloadLength
91
-	 * @return string
92
-	 * @throws ConnectionException
93
-	 */
94
-	private function getPayloadData(string $data, int $payloadLength): string
95
-	{
96
-		// Masking?
97
-		$mask = (bool)(ord($data[1]) >> 7);  // Bit 0 in byte 1
98
-		$payload = '';
99
-		$maskingKey = '';
100
-
101
-		// Get masking key.
102
-		if ($mask) {
103
-			$maskingKey = $this->read(4);
104
-		}
105
-
106
-		// Get the actual payload, if any (might not be for e.g. close frames.
107
-		if ($payloadLength > 0) {
108
-			$data = $this->read($payloadLength);
109
-
110
-			if ($mask) {
111
-				// Unmask payload.
112
-				for ($i = 0; $i < $payloadLength; $i++) {
113
-					$payload .= ($data[$i] ^ $maskingKey[$i % 4]);
114
-				}
115
-			} else {
116
-				$payload = $data;
117
-			}
118
-		}
119
-
120
-		return $payload;
121
-	}
122
-
123
-	/**
124
-	 * @return string|null
125
-	 * @throws \Exception
126
-	 */
127
-	protected function receiveFragment(): string|null
128
-	{
129
-		$data = $this->read(2);
130
-		if (is_string($data) === false) {
131
-			return null;
132
-		}
133
-
134
-		$final = (bool)(ord($data[0]) & 1 << 7);
135
-
136
-		$opcodeInt = ord($data[0]) & 31;
137
-		$opcodeInts = array_flip(self::$opcodes);
138
-		if (!array_key_exists($opcodeInt, $opcodeInts)) {
139
-			throw new ConnectionException(
140
-				"Bad opcode in websocket frame: $opcodeInt",
141
-				CommonsContract::CLIENT_BAD_OPCODE
142
-			);
143
-		}
144
-
145
-		$opcode = $opcodeInts[$opcodeInt];
146
-
147
-		if ($opcode !== 'continuation') {
148
-			$this->lastOpcode = $opcode;
149
-		}
150
-
151
-		$payloadLength = $this->getPayloadLength($data);
152
-		$payload = $this->getPayloadData($data, $payloadLength);
153
-
154
-		if ($opcode === CommonsContract::EVENT_TYPE_CLOSE) {
155
-			if ($payloadLength >= 2) {
156
-				$statusBin = $payload[0] . $payload[1];
157
-				$status = bindec(sprintf('%08b%08b', ord($payload[0]), ord($payload[1])));
158
-				$this->closeStatus = $status;
159
-				$payload = substr($payload, 2);
160
-
161
-				if (!$this->isClosing) {
162
-					$this->send($statusBin . 'Close acknowledged: ' . $status,
163
-						CommonsContract::EVENT_TYPE_CLOSE); // Respond.
164
-				}
165
-			}
166
-
167
-			if ($this->isClosing) {
168
-				$this->isClosing = false; // A close response, all done.
169
-			}
170
-
171
-			fclose($this->socket);
172
-			$this->isConnected = false;
173
-		}
174
-
175
-		if (!$final) {
176
-			$this->hugePayload .= $payload;
177
-
178
-			return null;
179
-		}
180
-
181
-		if ($this->hugePayload) {
182
-			$payload = $this->hugePayload .= $payload;
183
-			$this->hugePayload = null;
184
-		}
185
-
186
-		return $payload;
187
-	}
188
-
189
-	/**
190
-	 * @param $final
191
-	 * @param $payload
192
-	 * @param $opcode
193
-	 * @param $masked
194
-	 * @throws \Exception
195
-	 */
196
-	protected function sendFragment($final, $payload, $opcode, $masked): void
197
-	{
198
-		// Binary string for header.
199
-		$frameHeadBin = '';
200
-		// Write FIN, final fragment bit.
201
-		$frameHeadBin .= (bool)$final ? '1' : '0';
202
-		// RSV 1, 2, & 3 false and unused.
203
-		$frameHeadBin .= '000';
204
-		// Opcode rest of the byte.
205
-		$frameHeadBin .= sprintf('%04b', self::$opcodes[$opcode]);
206
-		// Use masking?
207
-		$frameHeadBin .= $masked ? '1' : '0';
208
-
209
-		// 7 bits of payload length...
210
-		$payloadLen = strlen($payload);
211
-		if ($payloadLen > self::MAX_BYTES_READ) {
212
-			$frameHeadBin .= decbin(self::MASK_127);
213
-			$frameHeadBin .= sprintf('%064b', $payloadLen);
214
-		} else if ($payloadLen > self::MASK_125) {
215
-			$frameHeadBin .= decbin(self::MASK_126);
216
-			$frameHeadBin .= sprintf('%016b', $payloadLen);
217
-		} else {
218
-			$frameHeadBin .= sprintf('%07b', $payloadLen);
219
-		}
220
-
221
-		$frame = '';
222
-
223
-		// Write frame head to frame.
224
-		foreach (str_split($frameHeadBin, 8) as $binstr) {
225
-			$frame .= chr(bindec($binstr));
226
-		}
227
-		// Handle masking
228
-		if ($masked) {
229
-			// generate a random mask:
230
-			$mask = '';
231
-			for ($i = 0; $i < 4; $i++) {
232
-				$mask .= chr(random_int(0, 255));
233
-			}
234
-			$frame .= $mask;
235
-		}
236
-
237
-		// Append payload to frame:
238
-		for ($i = 0; $i < $payloadLen; $i++) {
239
-			$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
240
-		}
241
-
242
-		$this->write($frame);
243
-	}
244
-
245
-	/**
246
-	 * Sec-WebSocket-Key generator
247
-	 *
248
-	 * @return string   the 16 character length key
249
-	 * @throws \Exception
250
-	 */
251
-	private function generateKey(): string
252
-	{
253
-		$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789';
254
-		$key = '';
255
-		$chLen = strlen($chars);
256
-		for ($i = 0; $i < self::KEY_GEN_LENGTH; $i++) {
257
-			$key .= $chars[random_int(0, $chLen - 1)];
258
-		}
259
-
260
-		return base64_encode($key);
261
-	}
20
+    /**
21
+     * Validates whether server sent valid upgrade response
22
+     *
23
+     * @param WebSocketConfig $config
24
+     * @param string $pathWithQuery
25
+     * @param string $key
26
+     * @throws ConnectionException
27
+     */
28
+    private function validateResponse(WebSocketConfig $config, string $pathWithQuery, string $key): void
29
+    {
30
+        $response = stream_get_line($this->socket, self::DEFAULT_RESPONSE_HEADER, "\r\n\r\n");
31
+        if (!preg_match(self::SEC_WEBSOCKET_ACCEPT_PTTRN, $response, $matches)) {
32
+            $address = $config->getScheme() . '://' . $config->getHost() . ':' . $config->getPort() . $pathWithQuery;
33
+            throw new ConnectionException(
34
+                "Connection to '{$address}' failed: Server sent invalid upgrade response:\n"
35
+                . $response, CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
36
+            );
37
+        }
38
+
39
+        $keyAccept = trim($matches[1]);
40
+        $expectedResponse = base64_encode(pack('H*', sha1($key . self::SERVER_KEY_ACCEPT)));
41
+        if ($keyAccept !== $expectedResponse) {
42
+            throw new ConnectionException(
43
+                'Server sent bad upgrade response.',
44
+                CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
45
+            );
46
+        }
47
+    }
48
+
49
+    /**
50
+     *  Gets host uri based on protocol
51
+     *
52
+     * @param WebSocketConfig $config
53
+     * @return string
54
+     * @throws BadUriException
55
+     */
56
+    private function getHostUri(WebSocketConfig $config): string
57
+    {
58
+        if (in_array($config->getScheme(), ['ws', 'wss'], true) === false) {
59
+            throw new BadUriException(
60
+                "Url should have scheme ws or wss, not '{$config->getScheme()}' from URI '$this->socketUrl' .",
61
+                CommonsContract::CLIENT_INCORRECT_SCHEME
62
+            );
63
+        }
64
+
65
+        return ($config->getScheme() === 'wss' ? 'ssl' : 'tcp') . '://' . $config->getHost();
66
+    }
67
+
68
+    /**
69
+     * @param string $data
70
+     * @return float|int
71
+     * @throws ConnectionException
72
+     */
73
+    private function getPayloadLength(string $data): float|int
74
+    {
75
+        $payloadLength = (int)ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
76
+        if ($payloadLength > self::MASK_125) {
77
+            if ($payloadLength === self::MASK_126) {
78
+                $data = $this->read(2); // 126: Payload is a 16-bit unsigned int
79
+            } else {
80
+                $data = $this->read(8); // 127: Payload is a 64-bit unsigned int
81
+            }
82
+            $payloadLength = bindec(self::sprintB($data));
83
+        }
84
+
85
+        return $payloadLength;
86
+    }
87
+
88
+    /**
89
+     * @param string $data
90
+     * @param int $payloadLength
91
+     * @return string
92
+     * @throws ConnectionException
93
+     */
94
+    private function getPayloadData(string $data, int $payloadLength): string
95
+    {
96
+        // Masking?
97
+        $mask = (bool)(ord($data[1]) >> 7);  // Bit 0 in byte 1
98
+        $payload = '';
99
+        $maskingKey = '';
100
+
101
+        // Get masking key.
102
+        if ($mask) {
103
+            $maskingKey = $this->read(4);
104
+        }
105
+
106
+        // Get the actual payload, if any (might not be for e.g. close frames.
107
+        if ($payloadLength > 0) {
108
+            $data = $this->read($payloadLength);
109
+
110
+            if ($mask) {
111
+                // Unmask payload.
112
+                for ($i = 0; $i < $payloadLength; $i++) {
113
+                    $payload .= ($data[$i] ^ $maskingKey[$i % 4]);
114
+                }
115
+            } else {
116
+                $payload = $data;
117
+            }
118
+        }
119
+
120
+        return $payload;
121
+    }
122
+
123
+    /**
124
+     * @return string|null
125
+     * @throws \Exception
126
+     */
127
+    protected function receiveFragment(): string|null
128
+    {
129
+        $data = $this->read(2);
130
+        if (is_string($data) === false) {
131
+            return null;
132
+        }
133
+
134
+        $final = (bool)(ord($data[0]) & 1 << 7);
135
+
136
+        $opcodeInt = ord($data[0]) & 31;
137
+        $opcodeInts = array_flip(self::$opcodes);
138
+        if (!array_key_exists($opcodeInt, $opcodeInts)) {
139
+            throw new ConnectionException(
140
+                "Bad opcode in websocket frame: $opcodeInt",
141
+                CommonsContract::CLIENT_BAD_OPCODE
142
+            );
143
+        }
144
+
145
+        $opcode = $opcodeInts[$opcodeInt];
146
+
147
+        if ($opcode !== 'continuation') {
148
+            $this->lastOpcode = $opcode;
149
+        }
150
+
151
+        $payloadLength = $this->getPayloadLength($data);
152
+        $payload = $this->getPayloadData($data, $payloadLength);
153
+
154
+        if ($opcode === CommonsContract::EVENT_TYPE_CLOSE) {
155
+            if ($payloadLength >= 2) {
156
+                $statusBin = $payload[0] . $payload[1];
157
+                $status = bindec(sprintf('%08b%08b', ord($payload[0]), ord($payload[1])));
158
+                $this->closeStatus = $status;
159
+                $payload = substr($payload, 2);
160
+
161
+                if (!$this->isClosing) {
162
+                    $this->send($statusBin . 'Close acknowledged: ' . $status,
163
+                        CommonsContract::EVENT_TYPE_CLOSE); // Respond.
164
+                }
165
+            }
166
+
167
+            if ($this->isClosing) {
168
+                $this->isClosing = false; // A close response, all done.
169
+            }
170
+
171
+            fclose($this->socket);
172
+            $this->isConnected = false;
173
+        }
174
+
175
+        if (!$final) {
176
+            $this->hugePayload .= $payload;
177
+
178
+            return null;
179
+        }
180
+
181
+        if ($this->hugePayload) {
182
+            $payload = $this->hugePayload .= $payload;
183
+            $this->hugePayload = null;
184
+        }
185
+
186
+        return $payload;
187
+    }
188
+
189
+    /**
190
+     * @param $final
191
+     * @param $payload
192
+     * @param $opcode
193
+     * @param $masked
194
+     * @throws \Exception
195
+     */
196
+    protected function sendFragment($final, $payload, $opcode, $masked): void
197
+    {
198
+        // Binary string for header.
199
+        $frameHeadBin = '';
200
+        // Write FIN, final fragment bit.
201
+        $frameHeadBin .= (bool)$final ? '1' : '0';
202
+        // RSV 1, 2, & 3 false and unused.
203
+        $frameHeadBin .= '000';
204
+        // Opcode rest of the byte.
205
+        $frameHeadBin .= sprintf('%04b', self::$opcodes[$opcode]);
206
+        // Use masking?
207
+        $frameHeadBin .= $masked ? '1' : '0';
208
+
209
+        // 7 bits of payload length...
210
+        $payloadLen = strlen($payload);
211
+        if ($payloadLen > self::MAX_BYTES_READ) {
212
+            $frameHeadBin .= decbin(self::MASK_127);
213
+            $frameHeadBin .= sprintf('%064b', $payloadLen);
214
+        } else if ($payloadLen > self::MASK_125) {
215
+            $frameHeadBin .= decbin(self::MASK_126);
216
+            $frameHeadBin .= sprintf('%016b', $payloadLen);
217
+        } else {
218
+            $frameHeadBin .= sprintf('%07b', $payloadLen);
219
+        }
220
+
221
+        $frame = '';
222
+
223
+        // Write frame head to frame.
224
+        foreach (str_split($frameHeadBin, 8) as $binstr) {
225
+            $frame .= chr(bindec($binstr));
226
+        }
227
+        // Handle masking
228
+        if ($masked) {
229
+            // generate a random mask:
230
+            $mask = '';
231
+            for ($i = 0; $i < 4; $i++) {
232
+                $mask .= chr(random_int(0, 255));
233
+            }
234
+            $frame .= $mask;
235
+        }
236
+
237
+        // Append payload to frame:
238
+        for ($i = 0; $i < $payloadLen; $i++) {
239
+            $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
240
+        }
241
+
242
+        $this->write($frame);
243
+    }
244
+
245
+    /**
246
+     * Sec-WebSocket-Key generator
247
+     *
248
+     * @return string   the 16 character length key
249
+     * @throws \Exception
250
+     */
251
+    private function generateKey(): string
252
+    {
253
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789';
254
+        $key = '';
255
+        $chLen = strlen($chars);
256
+        for ($i = 0; $i < self::KEY_GEN_LENGTH; $i++) {
257
+            $key .= $chars[random_int(0, $chLen - 1)];
258
+        }
259
+
260
+        return base64_encode($key);
261
+    }
262 262
 
263 263
 }
264 264
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -72,7 +72,7 @@  discard block
 block discarded – undo
72 72
 	 */
73 73
 	private function getPayloadLength(string $data): float|int
74 74
 	{
75
-		$payloadLength = (int)ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
75
+		$payloadLength = (int) ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
76 76
 		if ($payloadLength > self::MASK_125) {
77 77
 			if ($payloadLength === self::MASK_126) {
78 78
 				$data = $this->read(2); // 126: Payload is a 16-bit unsigned int
@@ -94,7 +94,7 @@  discard block
 block discarded – undo
94 94
 	private function getPayloadData(string $data, int $payloadLength): string
95 95
 	{
96 96
 		// Masking?
97
-		$mask = (bool)(ord($data[1]) >> 7);  // Bit 0 in byte 1
97
+		$mask = (bool) (ord($data[1]) >> 7); // Bit 0 in byte 1
98 98
 		$payload = '';
99 99
 		$maskingKey = '';
100 100
 
@@ -110,7 +110,7 @@  discard block
 block discarded – undo
110 110
 			if ($mask) {
111 111
 				// Unmask payload.
112 112
 				for ($i = 0; $i < $payloadLength; $i++) {
113
-					$payload .= ($data[$i] ^ $maskingKey[$i % 4]);
113
+					$payload .= ($data[$i]^$maskingKey[$i % 4]);
114 114
 				}
115 115
 			} else {
116 116
 				$payload = $data;
@@ -131,7 +131,7 @@  discard block
 block discarded – undo
131 131
 			return null;
132 132
 		}
133 133
 
134
-		$final = (bool)(ord($data[0]) & 1 << 7);
134
+		$final = (bool) (ord($data[0]) & 1 << 7);
135 135
 
136 136
 		$opcodeInt = ord($data[0]) & 31;
137 137
 		$opcodeInts = array_flip(self::$opcodes);
@@ -198,7 +198,7 @@  discard block
 block discarded – undo
198 198
 		// Binary string for header.
199 199
 		$frameHeadBin = '';
200 200
 		// Write FIN, final fragment bit.
201
-		$frameHeadBin .= (bool)$final ? '1' : '0';
201
+		$frameHeadBin .= (bool) $final ? '1' : '0';
202 202
 		// RSV 1, 2, & 3 false and unused.
203 203
 		$frameHeadBin .= '000';
204 204
 		// Opcode rest of the byte.
@@ -236,7 +236,7 @@  discard block
 block discarded – undo
236 236
 
237 237
 		// Append payload to frame:
238 238
 		for ($i = 0; $i < $payloadLen; $i++) {
239
-			$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
239
+			$frame .= ($masked === true) ? $payload[$i]^$mask[$i % 4] : $payload[$i];
240 240
 		}
241 241
 
242 242
 		$this->write($frame);
Please login to merge, or discard this patch.