Passed
Push — master ( d048db...e350b2 )
by Shahrad
01:41
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
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.
Indentation   +232 added lines, -232 removed lines patch added patch discarded remove patch
@@ -24,237 +24,237 @@
 block discarded – undo
24 24
 class WebSocket implements WscCommonsContract
25 25
 {
26 26
 
27
-	use WSClientTrait;
28
-	use WSConnectionTrait;
29
-
30
-	/**
31
-	 * App version
32
-	 *
33
-	 * @var string
34
-	 */
35
-	public const VERSION = 'v1.2.0';
36
-
37
-	/**
38
-	 * @var resource|bool
39
-	 */
40
-	private $socket;
41
-
42
-	/**
43
-	 * @var string
44
-	 */
45
-	private string $lastOpcode;
46
-
47
-	/**
48
-	 * @var float|int
49
-	 */
50
-	private float|int $closeStatus;
51
-
52
-	/**
53
-	 * @var string|null
54
-	 */
55
-	private ?string $hugePayload;
56
-
57
-	/**
58
-	 * @var WebSocketConfig
59
-	 */
60
-	protected WebSocketConfig $config;
61
-
62
-	/**
63
-	 * @var string
64
-	 */
65
-	protected string $socketUrl;
66
-
67
-	/**
68
-	 * @var array|int[]
69
-	 */
70
-	private static array $opcodes = [
71
-		CommonsContract::EVENT_TYPE_CONTINUATION => 0,
72
-		CommonsContract::EVENT_TYPE_TEXT => 1,
73
-		CommonsContract::EVENT_TYPE_BINARY => 2,
74
-		CommonsContract::EVENT_TYPE_CLOSE => 8,
75
-		CommonsContract::EVENT_TYPE_PING => 9,
76
-		CommonsContract::EVENT_TYPE_PONG => 10,
77
-	];
78
-
79
-	/**
80
-	 * Sets parameters for Web Socket Client intercommunication
81
-	 *
82
-	 * @param ?SocketClient $client leave it empty if you want to use default socket client
83
-	 */
84
-	public function __construct(?SocketClient $client = null)
85
-	{
86
-		if ($client instanceof SocketClient) {
87
-
88
-			$this->onOpen = function ($socket) use ($client) {
89
-				$client->onOpen($socket);
90
-			};
91
-
92
-			$this->onClose = function ($socket, int $closeStatus) use ($client) {
93
-				$client->onClose($socket, $closeStatus);
94
-			};
95
-
96
-			$this->onError = function ($socket, WebSocketException $exception) use ($client) {
97
-				$client->onError($socket, $exception);
98
-			};
99
-
100
-			$this->onMessage = function ($socket, string $message) use ($client) {
101
-				$client->onMessage($socket, $message);
102
-			};
103
-		}
104
-
105
-		$this->config = $config ?? new WebSocketConfig();
106
-	}
107
-
108
-	/**
109
-	 * Init a proxy connection
110
-	 *
111
-	 * @return resource|false
112
-	 * @throws \InvalidArgumentException
113
-	 * @throws ConnectionException
114
-	 */
115
-	private function proxy()
116
-	{
117
-		$sock = @stream_socket_client(
118
-			WscCommonsContract::TCP_SCHEME . $this->config->getProxyIp() . ':' . $this->config->getProxyPort(),
119
-			$errno,
120
-			$errstr,
121
-			$this->config->getTimeout(),
122
-			STREAM_CLIENT_CONNECT,
123
-			$this->getStreamContext()
124
-		);
125
-
126
-		$write = "CONNECT {$this->config->getProxyIp()}:{$this->config->getProxyPort()} HTTP/1.1\r\n";
127
-		$auth = $this->config->getProxyAuth();
128
-
129
-		if ($auth !== NULL) {
130
-			$write .= "Proxy-Authorization: Basic {$auth}\r\n";
131
-		}
132
-
133
-		$write .= "\r\n";
134
-		fwrite($sock, $write);
135
-		$resp = fread($sock, 1024);
136
-
137
-		if (preg_match(self::PROXY_MATCH_RESP, $resp) === 1) {
138
-			return $sock;
139
-		}
140
-
141
-		throw new ConnectionException('Failed to connect to the host via proxy');
142
-	}
143
-
144
-	/**
145
-	 * @return mixed
146
-	 * @throws \InvalidArgumentException
147
-	 */
148
-	private function getStreamContext(): mixed
149
-	{
150
-		if ($this->config->getContext() !== null) {
151
-			// Suppress the error since we'll catch it below
152
-			if (@get_resource_type($this->config->getContext()) === 'stream-context') {
153
-				return $this->config->getContext();
154
-			}
155
-
156
-			throw new \InvalidArgumentException(
157
-				'Stream context is invalid',
158
-				CommonsContract::CLIENT_INVALID_STREAM_CONTEXT
159
-			);
160
-		}
161
-
162
-		return stream_context_create($this->config->getContextOptions());
163
-	}
164
-
165
-	/**
166
-	 * @param mixed $urlParts
167
-	 *
168
-	 * @return string
169
-	 */
170
-	private function getPathWithQuery(mixed $urlParts): string
171
-	{
172
-		$path = $urlParts['path'] ?? '/';
173
-		$query = $urlParts['query'] ?? '';
174
-		$fragment = $urlParts['fragment'] ?? '';
175
-		$pathWithQuery = $path;
176
-
177
-		if (!empty($query)) {
178
-			$pathWithQuery .= '?' . $query;
179
-		}
180
-
181
-		if (!empty($fragment)) {
182
-			$pathWithQuery .= '#' . $fragment;
183
-		}
184
-
185
-		return $pathWithQuery;
186
-	}
187
-
188
-	/**
189
-	 * @param string $pathWithQuery
190
-	 * @param array $headers
191
-	 *
192
-	 * @return string
193
-	 */
194
-	private function getHeaders(string $pathWithQuery, array $headers): string
195
-	{
196
-		return 'GET ' . $pathWithQuery . " HTTP/1.1\r\n"
197
-			. implode(
198
-				"\r\n",
199
-				array_map(
200
-					function ($key, $value) {
201
-						return "$key: $value";
202
-					},
203
-					array_keys($headers),
204
-					$headers
205
-				)
206
-			)
207
-			. "\r\n\r\n";
208
-	}
209
-
210
-	/**
211
-	 * @param int $timeout
212
-	 * @param null $microSecs
213
-	 *
214
-	 * @return void
215
-	 */
216
-	public function setTimeout(int $timeout, $microSecs = null): void
217
-	{
218
-		$this->config->setTimeout($timeout);
219
-		if ($this->socket && get_resource_type($this->socket) === 'stream') {
220
-			stream_set_timeout($this->socket, $timeout, $microSecs);
221
-		}
222
-	}
223
-
224
-	/**
225
-	 * @param string $name
226
-	 * @param array $arguments
227
-	 *
228
-	 * @return mixed
229
-	 * @throws \Exception
230
-	 */
231
-	public function __call(string $name, array $arguments): mixed
232
-	{
233
-		if (property_exists($this, $name)) {
234
-			return $this->$name;
235
-		}
236
-
237
-		if (method_exists($this, $name)) {
238
-			return call_user_func_array([$this, $name], $arguments);
239
-		}
240
-
241
-		if (str_starts_with($name, 'get')) {
242
-			$property = lcfirst(substr($name, 3));
243
-
244
-			if (property_exists($this, $property)) {
245
-				return $this->{$property};
246
-			}
247
-		}
248
-
249
-		if (str_starts_with($name, 'set')) {
250
-			$property = lcfirst(substr($name, 3));
251
-			if (property_exists($this, $property)) {
252
-				$this->{$property} = $arguments[0];
253
-				return $this;
254
-			}
255
-		}
256
-
257
-		throw new \Exception(sprintf("Method '%s' does not exist.", $name));
258
-	}
27
+    use WSClientTrait;
28
+    use WSConnectionTrait;
29
+
30
+    /**
31
+     * App version
32
+     *
33
+     * @var string
34
+     */
35
+    public const VERSION = 'v1.2.0';
36
+
37
+    /**
38
+     * @var resource|bool
39
+     */
40
+    private $socket;
41
+
42
+    /**
43
+     * @var string
44
+     */
45
+    private string $lastOpcode;
46
+
47
+    /**
48
+     * @var float|int
49
+     */
50
+    private float|int $closeStatus;
51
+
52
+    /**
53
+     * @var string|null
54
+     */
55
+    private ?string $hugePayload;
56
+
57
+    /**
58
+     * @var WebSocketConfig
59
+     */
60
+    protected WebSocketConfig $config;
61
+
62
+    /**
63
+     * @var string
64
+     */
65
+    protected string $socketUrl;
66
+
67
+    /**
68
+     * @var array|int[]
69
+     */
70
+    private static array $opcodes = [
71
+        CommonsContract::EVENT_TYPE_CONTINUATION => 0,
72
+        CommonsContract::EVENT_TYPE_TEXT => 1,
73
+        CommonsContract::EVENT_TYPE_BINARY => 2,
74
+        CommonsContract::EVENT_TYPE_CLOSE => 8,
75
+        CommonsContract::EVENT_TYPE_PING => 9,
76
+        CommonsContract::EVENT_TYPE_PONG => 10,
77
+    ];
78
+
79
+    /**
80
+     * Sets parameters for Web Socket Client intercommunication
81
+     *
82
+     * @param ?SocketClient $client leave it empty if you want to use default socket client
83
+     */
84
+    public function __construct(?SocketClient $client = null)
85
+    {
86
+        if ($client instanceof SocketClient) {
87
+
88
+            $this->onOpen = function ($socket) use ($client) {
89
+                $client->onOpen($socket);
90
+            };
91
+
92
+            $this->onClose = function ($socket, int $closeStatus) use ($client) {
93
+                $client->onClose($socket, $closeStatus);
94
+            };
95
+
96
+            $this->onError = function ($socket, WebSocketException $exception) use ($client) {
97
+                $client->onError($socket, $exception);
98
+            };
99
+
100
+            $this->onMessage = function ($socket, string $message) use ($client) {
101
+                $client->onMessage($socket, $message);
102
+            };
103
+        }
104
+
105
+        $this->config = $config ?? new WebSocketConfig();
106
+    }
107
+
108
+    /**
109
+     * Init a proxy connection
110
+     *
111
+     * @return resource|false
112
+     * @throws \InvalidArgumentException
113
+     * @throws ConnectionException
114
+     */
115
+    private function proxy()
116
+    {
117
+        $sock = @stream_socket_client(
118
+            WscCommonsContract::TCP_SCHEME . $this->config->getProxyIp() . ':' . $this->config->getProxyPort(),
119
+            $errno,
120
+            $errstr,
121
+            $this->config->getTimeout(),
122
+            STREAM_CLIENT_CONNECT,
123
+            $this->getStreamContext()
124
+        );
125
+
126
+        $write = "CONNECT {$this->config->getProxyIp()}:{$this->config->getProxyPort()} HTTP/1.1\r\n";
127
+        $auth = $this->config->getProxyAuth();
128
+
129
+        if ($auth !== NULL) {
130
+            $write .= "Proxy-Authorization: Basic {$auth}\r\n";
131
+        }
132
+
133
+        $write .= "\r\n";
134
+        fwrite($sock, $write);
135
+        $resp = fread($sock, 1024);
136
+
137
+        if (preg_match(self::PROXY_MATCH_RESP, $resp) === 1) {
138
+            return $sock;
139
+        }
140
+
141
+        throw new ConnectionException('Failed to connect to the host via proxy');
142
+    }
143
+
144
+    /**
145
+     * @return mixed
146
+     * @throws \InvalidArgumentException
147
+     */
148
+    private function getStreamContext(): mixed
149
+    {
150
+        if ($this->config->getContext() !== null) {
151
+            // Suppress the error since we'll catch it below
152
+            if (@get_resource_type($this->config->getContext()) === 'stream-context') {
153
+                return $this->config->getContext();
154
+            }
155
+
156
+            throw new \InvalidArgumentException(
157
+                'Stream context is invalid',
158
+                CommonsContract::CLIENT_INVALID_STREAM_CONTEXT
159
+            );
160
+        }
161
+
162
+        return stream_context_create($this->config->getContextOptions());
163
+    }
164
+
165
+    /**
166
+     * @param mixed $urlParts
167
+     *
168
+     * @return string
169
+     */
170
+    private function getPathWithQuery(mixed $urlParts): string
171
+    {
172
+        $path = $urlParts['path'] ?? '/';
173
+        $query = $urlParts['query'] ?? '';
174
+        $fragment = $urlParts['fragment'] ?? '';
175
+        $pathWithQuery = $path;
176
+
177
+        if (!empty($query)) {
178
+            $pathWithQuery .= '?' . $query;
179
+        }
180
+
181
+        if (!empty($fragment)) {
182
+            $pathWithQuery .= '#' . $fragment;
183
+        }
184
+
185
+        return $pathWithQuery;
186
+    }
187
+
188
+    /**
189
+     * @param string $pathWithQuery
190
+     * @param array $headers
191
+     *
192
+     * @return string
193
+     */
194
+    private function getHeaders(string $pathWithQuery, array $headers): string
195
+    {
196
+        return 'GET ' . $pathWithQuery . " HTTP/1.1\r\n"
197
+            . implode(
198
+                "\r\n",
199
+                array_map(
200
+                    function ($key, $value) {
201
+                        return "$key: $value";
202
+                    },
203
+                    array_keys($headers),
204
+                    $headers
205
+                )
206
+            )
207
+            . "\r\n\r\n";
208
+    }
209
+
210
+    /**
211
+     * @param int $timeout
212
+     * @param null $microSecs
213
+     *
214
+     * @return void
215
+     */
216
+    public function setTimeout(int $timeout, $microSecs = null): void
217
+    {
218
+        $this->config->setTimeout($timeout);
219
+        if ($this->socket && get_resource_type($this->socket) === 'stream') {
220
+            stream_set_timeout($this->socket, $timeout, $microSecs);
221
+        }
222
+    }
223
+
224
+    /**
225
+     * @param string $name
226
+     * @param array $arguments
227
+     *
228
+     * @return mixed
229
+     * @throws \Exception
230
+     */
231
+    public function __call(string $name, array $arguments): mixed
232
+    {
233
+        if (property_exists($this, $name)) {
234
+            return $this->$name;
235
+        }
236
+
237
+        if (method_exists($this, $name)) {
238
+            return call_user_func_array([$this, $name], $arguments);
239
+        }
240
+
241
+        if (str_starts_with($name, 'get')) {
242
+            $property = lcfirst(substr($name, 3));
243
+
244
+            if (property_exists($this, $property)) {
245
+                return $this->{$property};
246
+            }
247
+        }
248
+
249
+        if (str_starts_with($name, 'set')) {
250
+            $property = lcfirst(substr($name, 3));
251
+            if (property_exists($this, $property)) {
252
+                $this->{$property} = $arguments[0];
253
+                return $this;
254
+            }
255
+        }
256
+
257
+        throw new \Exception(sprintf("Method '%s' does not exist.", $name));
258
+    }
259 259
 
260 260
 }
261 261
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/WSClientTrait.php 2 patches
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.
Indentation   +266 added lines, -266 removed lines patch added patch discarded remove patch
@@ -19,271 +19,271 @@
 block discarded – undo
19 19
 trait WSClientTrait
20 20
 {
21 21
 
22
-	/**
23
-	 * Validates whether server sent valid upgrade response
24
-	 *
25
-	 * @param WebSocketConfig $config
26
-	 * @param string $pathWithQuery
27
-	 * @param string $key
28
-	 * @throws ConnectionException
29
-	 */
30
-	private function validateResponse(WebSocketConfig $config, string $pathWithQuery, string $key): void
31
-	{
32
-		$response = stream_get_line($this->socket, self::DEFAULT_RESPONSE_HEADER, "\r\n\r\n");
33
-		if (!preg_match(self::SEC_WEBSOCKET_ACCEPT_PTTRN, $response, $matches)) {
34
-			$address = $config->getScheme() . '://' . $config->getHost() . ':' . $config->getPort() . $pathWithQuery;
35
-			throw new ConnectionException(
36
-				"Connection to '{$address}' failed: Server sent invalid upgrade response:\n"
37
-				. $response, CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
38
-			);
39
-		}
40
-
41
-		$keyAccept = trim($matches[1]);
42
-		$expectedResponse = base64_encode(pack('H*', sha1($key . self::SERVER_KEY_ACCEPT)));
43
-		if ($keyAccept !== $expectedResponse) {
44
-			throw new ConnectionException(
45
-				'Server sent bad upgrade response.',
46
-				CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
47
-			);
48
-		}
49
-	}
50
-
51
-	/**
52
-	 *  Gets host uri based on protocol
53
-	 *
54
-	 * @param WebSocketConfig $config
55
-	 * @return string
56
-	 * @throws BadUriException
57
-	 */
58
-	private function getHostUri(WebSocketConfig $config): string
59
-	{
60
-		if (in_array($config->getScheme(), ['ws', 'wss'], true) === false) {
61
-			throw new BadUriException(
62
-				"Url should have scheme ws or wss, not '{$config->getScheme()}' from URI '$this->socketUrl' .",
63
-				CommonsContract::CLIENT_INCORRECT_SCHEME
64
-			);
65
-		}
66
-
67
-		return ($config->getScheme() === 'wss' ? 'ssl' : 'tcp') . '://' . $config->getHost();
68
-	}
69
-
70
-	/**
71
-	 * @param string $data
72
-	 * @return float|int
73
-	 * @throws ConnectionException
74
-	 */
75
-	private function getPayloadLength(string $data): float|int
76
-	{
77
-		$payloadLength = (int)ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
78
-		if ($payloadLength > self::MASK_125) {
79
-			if ($payloadLength === self::MASK_126) {
80
-				$data = $this->read(2); // 126: Payload is a 16-bit unsigned int
81
-			} else {
82
-				$data = $this->read(8); // 127: Payload is a 64-bit unsigned int
83
-			}
84
-			$payloadLength = bindec(Toolkit::sprintB($data));
85
-		}
86
-
87
-		return $payloadLength;
88
-	}
89
-
90
-	/**
91
-	 * @param string $data
92
-	 * @param int $payloadLength
93
-	 * @return string
94
-	 * @throws ConnectionException
95
-	 */
96
-	private function getPayloadData(string $data, int $payloadLength): string
97
-	{
98
-		// Masking?
99
-		$mask = (bool)(ord($data[1]) >> 7);  // Bit 0 in byte 1
100
-		$payload = '';
101
-		$maskingKey = '';
102
-
103
-		// Get masking key.
104
-		if ($mask) {
105
-			$maskingKey = $this->read(4);
106
-		}
107
-
108
-		// Get the actual payload, if any (might not be for e.g. close frames.
109
-		if ($payloadLength > 0) {
110
-			$data = $this->read($payloadLength);
111
-
112
-			if ($mask) {
113
-				// Unmask payload.
114
-				for ($i = 0; $i < $payloadLength; $i++) {
115
-					$payload .= ($data[$i] ^ $maskingKey[$i % 4]);
116
-				}
117
-			} else {
118
-				$payload = $data;
119
-			}
120
-		}
121
-
122
-		return $payload;
123
-	}
124
-
125
-	/**
126
-	 * @return string|null
127
-	 * @throws \Exception
128
-	 */
129
-	protected function receiveFragment(): string|null
130
-	{
131
-		$data = $this->read(2);
132
-		if (is_string($data) === false) {
133
-			return null;
134
-		}
135
-
136
-		$final = (bool)(ord($data[0]) & 1 << 7);
137
-
138
-		$opcodeInt = ord($data[0]) & 31;
139
-		$opcodeInts = array_flip(self::$opcodes);
140
-		if (!array_key_exists($opcodeInt, $opcodeInts)) {
141
-			throw new ConnectionException(
142
-				"Bad opcode in websocket frame: $opcodeInt",
143
-				CommonsContract::CLIENT_BAD_OPCODE
144
-			);
145
-		}
146
-
147
-		$opcode = $opcodeInts[$opcodeInt];
148
-
149
-		if ($opcode !== 'continuation') {
150
-			$this->lastOpcode = $opcode;
151
-		}
152
-
153
-		$payloadLength = $this->getPayloadLength($data);
154
-		$payload = $this->getPayloadData($data, $payloadLength);
155
-
156
-		if ($opcode === CommonsContract::EVENT_TYPE_CLOSE) {
157
-			if ($payloadLength >= 2) {
158
-				$statusBin = $payload[0] . $payload[1];
159
-				$status = bindec(sprintf('%08b%08b', ord($payload[0]), ord($payload[1])));
160
-				$this->closeStatus = $status;
161
-				$payload = substr($payload, 2);
162
-
163
-				if (!$this->isClosing) {
164
-					$this->send($statusBin . 'Close acknowledged: ' . $status,
165
-						CommonsContract::EVENT_TYPE_CLOSE); // Respond.
166
-				}
167
-			}
168
-
169
-			if ($this->isClosing) {
170
-				$this->isClosing = false; // A close response, all done.
171
-			}
172
-
173
-			fclose($this->socket);
174
-			$this->isConnected = false;
175
-		}
176
-
177
-		if (!$final) {
178
-			$this->hugePayload .= $payload;
179
-
180
-			return null;
181
-		}
182
-
183
-		if ($this->hugePayload) {
184
-			$payload = $this->hugePayload .= $payload;
185
-			$this->hugePayload = null;
186
-		}
187
-
188
-		return $payload;
189
-	}
190
-
191
-	/**
192
-	 * @param $final
193
-	 * @param $payload
194
-	 * @param $opcode
195
-	 * @param $masked
196
-	 * @throws \Exception
197
-	 */
198
-	protected function sendFragment($final, $payload, $opcode, $masked): void
199
-	{
200
-		// Binary string for header.
201
-		$frameHeadBin = '';
202
-		// Write FIN, final fragment bit.
203
-		$frameHeadBin .= (bool)$final ? '1' : '0';
204
-		// RSV 1, 2, & 3 false and unused.
205
-		$frameHeadBin .= '000';
206
-		// Opcode rest of the byte.
207
-		$frameHeadBin .= sprintf('%04b', self::$opcodes[$opcode]);
208
-		// Use masking?
209
-		$frameHeadBin .= $masked ? '1' : '0';
210
-
211
-		// 7 bits of payload length...
212
-		$payloadLen = strlen($payload);
213
-		if ($payloadLen > self::MAX_BYTES_READ) {
214
-			$frameHeadBin .= decbin(self::MASK_127);
215
-			$frameHeadBin .= sprintf('%064b', $payloadLen);
216
-		} else if ($payloadLen > self::MASK_125) {
217
-			$frameHeadBin .= decbin(self::MASK_126);
218
-			$frameHeadBin .= sprintf('%016b', $payloadLen);
219
-		} else {
220
-			$frameHeadBin .= sprintf('%07b', $payloadLen);
221
-		}
222
-
223
-		$frame = '';
224
-
225
-		// Write frame head to frame.
226
-		foreach (str_split($frameHeadBin, 8) as $binstr) {
227
-			$frame .= chr(bindec($binstr));
228
-		}
229
-		// Handle masking
230
-		if ($masked) {
231
-			// generate a random mask:
232
-			$mask = '';
233
-			for ($i = 0; $i < 4; $i++) {
234
-				$mask .= chr(random_int(0, 255));
235
-			}
236
-			$frame .= $mask;
237
-		}
238
-
239
-		// Append payload to frame:
240
-		for ($i = 0; $i < $payloadLen; $i++) {
241
-			$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
242
-		}
243
-
244
-		$this->write($frame);
245
-	}
246
-
247
-	/**
248
-	 * Sec-WebSocket-Key generator
249
-	 *
250
-	 * @return string   the 16 character length key
251
-	 * @throws \Exception
252
-	 */
253
-	private function generateKey(): string
254
-	{
255
-		$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789';
256
-		$key = '';
257
-		$chLen = strlen($chars);
258
-		for ($i = 0; $i < self::KEY_GEN_LENGTH; $i++) {
259
-			$key .= $chars[random_int(0, $chLen - 1)];
260
-		}
261
-
262
-		return base64_encode($key);
263
-	}
264
-
265
-	/**
266
-	 * @param int $len
267
-	 * @return string|null
268
-	 * @throws ConnectionException
269
-	 */
270
-	protected function read(int $len): string|null
271
-	{
272
-		if ($this->socket && $this->isConnected()) {
273
-			return Middleware::stream_read($this->socket, $len);
274
-		}
275
-
276
-		return null;
277
-	}
278
-
279
-	/**
280
-	 * @param string $data
281
-	 * @return void
282
-	 * @throws ConnectionException
283
-	 */
284
-	protected function write(string $data): void
285
-	{
286
-		Middleware::stream_write($this->socket, $data);
287
-	}
22
+    /**
23
+     * Validates whether server sent valid upgrade response
24
+     *
25
+     * @param WebSocketConfig $config
26
+     * @param string $pathWithQuery
27
+     * @param string $key
28
+     * @throws ConnectionException
29
+     */
30
+    private function validateResponse(WebSocketConfig $config, string $pathWithQuery, string $key): void
31
+    {
32
+        $response = stream_get_line($this->socket, self::DEFAULT_RESPONSE_HEADER, "\r\n\r\n");
33
+        if (!preg_match(self::SEC_WEBSOCKET_ACCEPT_PTTRN, $response, $matches)) {
34
+            $address = $config->getScheme() . '://' . $config->getHost() . ':' . $config->getPort() . $pathWithQuery;
35
+            throw new ConnectionException(
36
+                "Connection to '{$address}' failed: Server sent invalid upgrade response:\n"
37
+                . $response, CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
38
+            );
39
+        }
40
+
41
+        $keyAccept = trim($matches[1]);
42
+        $expectedResponse = base64_encode(pack('H*', sha1($key . self::SERVER_KEY_ACCEPT)));
43
+        if ($keyAccept !== $expectedResponse) {
44
+            throw new ConnectionException(
45
+                'Server sent bad upgrade response.',
46
+                CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
47
+            );
48
+        }
49
+    }
50
+
51
+    /**
52
+     *  Gets host uri based on protocol
53
+     *
54
+     * @param WebSocketConfig $config
55
+     * @return string
56
+     * @throws BadUriException
57
+     */
58
+    private function getHostUri(WebSocketConfig $config): string
59
+    {
60
+        if (in_array($config->getScheme(), ['ws', 'wss'], true) === false) {
61
+            throw new BadUriException(
62
+                "Url should have scheme ws or wss, not '{$config->getScheme()}' from URI '$this->socketUrl' .",
63
+                CommonsContract::CLIENT_INCORRECT_SCHEME
64
+            );
65
+        }
66
+
67
+        return ($config->getScheme() === 'wss' ? 'ssl' : 'tcp') . '://' . $config->getHost();
68
+    }
69
+
70
+    /**
71
+     * @param string $data
72
+     * @return float|int
73
+     * @throws ConnectionException
74
+     */
75
+    private function getPayloadLength(string $data): float|int
76
+    {
77
+        $payloadLength = (int)ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
78
+        if ($payloadLength > self::MASK_125) {
79
+            if ($payloadLength === self::MASK_126) {
80
+                $data = $this->read(2); // 126: Payload is a 16-bit unsigned int
81
+            } else {
82
+                $data = $this->read(8); // 127: Payload is a 64-bit unsigned int
83
+            }
84
+            $payloadLength = bindec(Toolkit::sprintB($data));
85
+        }
86
+
87
+        return $payloadLength;
88
+    }
89
+
90
+    /**
91
+     * @param string $data
92
+     * @param int $payloadLength
93
+     * @return string
94
+     * @throws ConnectionException
95
+     */
96
+    private function getPayloadData(string $data, int $payloadLength): string
97
+    {
98
+        // Masking?
99
+        $mask = (bool)(ord($data[1]) >> 7);  // Bit 0 in byte 1
100
+        $payload = '';
101
+        $maskingKey = '';
102
+
103
+        // Get masking key.
104
+        if ($mask) {
105
+            $maskingKey = $this->read(4);
106
+        }
107
+
108
+        // Get the actual payload, if any (might not be for e.g. close frames.
109
+        if ($payloadLength > 0) {
110
+            $data = $this->read($payloadLength);
111
+
112
+            if ($mask) {
113
+                // Unmask payload.
114
+                for ($i = 0; $i < $payloadLength; $i++) {
115
+                    $payload .= ($data[$i] ^ $maskingKey[$i % 4]);
116
+                }
117
+            } else {
118
+                $payload = $data;
119
+            }
120
+        }
121
+
122
+        return $payload;
123
+    }
124
+
125
+    /**
126
+     * @return string|null
127
+     * @throws \Exception
128
+     */
129
+    protected function receiveFragment(): string|null
130
+    {
131
+        $data = $this->read(2);
132
+        if (is_string($data) === false) {
133
+            return null;
134
+        }
135
+
136
+        $final = (bool)(ord($data[0]) & 1 << 7);
137
+
138
+        $opcodeInt = ord($data[0]) & 31;
139
+        $opcodeInts = array_flip(self::$opcodes);
140
+        if (!array_key_exists($opcodeInt, $opcodeInts)) {
141
+            throw new ConnectionException(
142
+                "Bad opcode in websocket frame: $opcodeInt",
143
+                CommonsContract::CLIENT_BAD_OPCODE
144
+            );
145
+        }
146
+
147
+        $opcode = $opcodeInts[$opcodeInt];
148
+
149
+        if ($opcode !== 'continuation') {
150
+            $this->lastOpcode = $opcode;
151
+        }
152
+
153
+        $payloadLength = $this->getPayloadLength($data);
154
+        $payload = $this->getPayloadData($data, $payloadLength);
155
+
156
+        if ($opcode === CommonsContract::EVENT_TYPE_CLOSE) {
157
+            if ($payloadLength >= 2) {
158
+                $statusBin = $payload[0] . $payload[1];
159
+                $status = bindec(sprintf('%08b%08b', ord($payload[0]), ord($payload[1])));
160
+                $this->closeStatus = $status;
161
+                $payload = substr($payload, 2);
162
+
163
+                if (!$this->isClosing) {
164
+                    $this->send($statusBin . 'Close acknowledged: ' . $status,
165
+                        CommonsContract::EVENT_TYPE_CLOSE); // Respond.
166
+                }
167
+            }
168
+
169
+            if ($this->isClosing) {
170
+                $this->isClosing = false; // A close response, all done.
171
+            }
172
+
173
+            fclose($this->socket);
174
+            $this->isConnected = false;
175
+        }
176
+
177
+        if (!$final) {
178
+            $this->hugePayload .= $payload;
179
+
180
+            return null;
181
+        }
182
+
183
+        if ($this->hugePayload) {
184
+            $payload = $this->hugePayload .= $payload;
185
+            $this->hugePayload = null;
186
+        }
187
+
188
+        return $payload;
189
+    }
190
+
191
+    /**
192
+     * @param $final
193
+     * @param $payload
194
+     * @param $opcode
195
+     * @param $masked
196
+     * @throws \Exception
197
+     */
198
+    protected function sendFragment($final, $payload, $opcode, $masked): void
199
+    {
200
+        // Binary string for header.
201
+        $frameHeadBin = '';
202
+        // Write FIN, final fragment bit.
203
+        $frameHeadBin .= (bool)$final ? '1' : '0';
204
+        // RSV 1, 2, & 3 false and unused.
205
+        $frameHeadBin .= '000';
206
+        // Opcode rest of the byte.
207
+        $frameHeadBin .= sprintf('%04b', self::$opcodes[$opcode]);
208
+        // Use masking?
209
+        $frameHeadBin .= $masked ? '1' : '0';
210
+
211
+        // 7 bits of payload length...
212
+        $payloadLen = strlen($payload);
213
+        if ($payloadLen > self::MAX_BYTES_READ) {
214
+            $frameHeadBin .= decbin(self::MASK_127);
215
+            $frameHeadBin .= sprintf('%064b', $payloadLen);
216
+        } else if ($payloadLen > self::MASK_125) {
217
+            $frameHeadBin .= decbin(self::MASK_126);
218
+            $frameHeadBin .= sprintf('%016b', $payloadLen);
219
+        } else {
220
+            $frameHeadBin .= sprintf('%07b', $payloadLen);
221
+        }
222
+
223
+        $frame = '';
224
+
225
+        // Write frame head to frame.
226
+        foreach (str_split($frameHeadBin, 8) as $binstr) {
227
+            $frame .= chr(bindec($binstr));
228
+        }
229
+        // Handle masking
230
+        if ($masked) {
231
+            // generate a random mask:
232
+            $mask = '';
233
+            for ($i = 0; $i < 4; $i++) {
234
+                $mask .= chr(random_int(0, 255));
235
+            }
236
+            $frame .= $mask;
237
+        }
238
+
239
+        // Append payload to frame:
240
+        for ($i = 0; $i < $payloadLen; $i++) {
241
+            $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
242
+        }
243
+
244
+        $this->write($frame);
245
+    }
246
+
247
+    /**
248
+     * Sec-WebSocket-Key generator
249
+     *
250
+     * @return string   the 16 character length key
251
+     * @throws \Exception
252
+     */
253
+    private function generateKey(): string
254
+    {
255
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789';
256
+        $key = '';
257
+        $chLen = strlen($chars);
258
+        for ($i = 0; $i < self::KEY_GEN_LENGTH; $i++) {
259
+            $key .= $chars[random_int(0, $chLen - 1)];
260
+        }
261
+
262
+        return base64_encode($key);
263
+    }
264
+
265
+    /**
266
+     * @param int $len
267
+     * @return string|null
268
+     * @throws ConnectionException
269
+     */
270
+    protected function read(int $len): string|null
271
+    {
272
+        if ($this->socket && $this->isConnected()) {
273
+            return Middleware::stream_read($this->socket, $len);
274
+        }
275
+
276
+        return null;
277
+    }
278
+
279
+    /**
280
+     * @param string $data
281
+     * @return void
282
+     * @throws ConnectionException
283
+     */
284
+    protected function write(string $data): void
285
+    {
286
+        Middleware::stream_write($this->socket, $data);
287
+    }
288 288
 
289 289
 }
290 290
\ No newline at end of file
Please login to merge, or discard this patch.
src/WebSocketConfig.php 1 patch
Indentation   +331 added lines, -331 removed lines patch added patch discarded remove patch
@@ -14,336 +14,336 @@
 block discarded – undo
14 14
 class WebSocketConfig
15 15
 {
16 16
 
17
-	/**
18
-	 * @var string
19
-	 */
20
-	private string $scheme;
21
-
22
-	/**
23
-	 * @var string
24
-	 */
25
-	private string $host;
26
-
27
-	/**
28
-	 * @var string
29
-	 */
30
-	private string $user;
31
-
32
-	/**
33
-	 * @var string
34
-	 */
35
-	private string $password;
36
-
37
-	/**
38
-	 * @var string
39
-	 */
40
-	private string $port;
41
-
42
-	/**
43
-	 * @var int
44
-	 */
45
-	private int $timeout = WscCommonsContract::DEFAULT_TIMEOUT;
46
-
47
-	/**
48
-	 * @var array
49
-	 */
50
-	private array $headers = [];
51
-
52
-	/**
53
-	 * @var int
54
-	 */
55
-	private int $fragmentSize = WscCommonsContract::DEFAULT_FRAGMENT_SIZE;
56
-
57
-	/**
58
-	 * @var null|resource
59
-	 */
60
-	private $context;
61
-
62
-	/**
63
-	 * @var bool
64
-	 */
65
-	private bool $hasProxy = false;
66
-
67
-	/**
68
-	 * @var string
69
-	 */
70
-	private string $proxyIp;
71
-
72
-	/**
73
-	 * @var string
74
-	 */
75
-	private string $proxyPort;
76
-
77
-	/**
78
-	 * @var string|null
79
-	 */
80
-	private ?string $proxyAuth;
81
-
82
-	/**
83
-	 * @var array
84
-	 */
85
-	private array $contextOptions = [];
86
-
87
-	/**
88
-	 * @var int
89
-	 */
90
-	private int $pingInterval = WscCommonsContract::DEFAULT_PING_INTERVAL;
91
-
92
-	/**
93
-	 * @return int
94
-	 */
95
-	public function getTimeout(): int
96
-	{
97
-		return $this->timeout;
98
-	}
99
-
100
-	/**
101
-	 * @param int $timeout
102
-	 * @return WebSocketConfig
103
-	 */
104
-	public function setTimeout(int $timeout): WebSocketConfig
105
-	{
106
-		$this->timeout = $timeout;
107
-		return $this;
108
-	}
109
-
110
-	/**
111
-	 * @param int $int
112
-	 * @return $this
113
-	 */
114
-	public function setPingInterval(int $int): WebSocketConfig
115
-	{
116
-		$this->pingInterval = $int;
117
-		return $this;
118
-	}
119
-
120
-	/**
121
-	 * @return int
122
-	 */
123
-	public function getPingInterval(): int
124
-	{
125
-		return $this->pingInterval;
126
-	}
127
-
128
-	/**
129
-	 * @return array
130
-	 */
131
-	public function getHeaders(): array
132
-	{
133
-		return $this->headers;
134
-	}
135
-
136
-	/**
137
-	 * @param array $headers
138
-	 * @return WebSocketConfig
139
-	 */
140
-	public function setHeaders(array $headers): WebSocketConfig
141
-	{
142
-		$this->headers = $headers;
143
-		return $this;
144
-	}
145
-
146
-	/**
147
-	 * @return int
148
-	 */
149
-	public function getFragmentSize(): int
150
-	{
151
-		return $this->fragmentSize;
152
-	}
153
-
154
-	/**
155
-	 * @param int $fragmentSize
156
-	 * @return WebSocketConfig
157
-	 */
158
-	public function setFragmentSize(int $fragmentSize): WebSocketConfig
159
-	{
160
-		$this->fragmentSize = $fragmentSize;
161
-		return $this;
162
-	}
163
-
164
-	/**
165
-	 * @return mixed
166
-	 */
167
-	public function getContext(): mixed
168
-	{
169
-		return $this->context;
170
-	}
171
-
172
-	/**
173
-	 * @param mixed $context
174
-	 * @return WebSocketConfig
175
-	 */
176
-	public function setContext(mixed $context): WebSocketConfig
177
-	{
178
-		$this->context = $context;
179
-		return $this;
180
-	}
181
-
182
-	/**
183
-	 * @return mixed
184
-	 */
185
-	public function getScheme(): string
186
-	{
187
-		return $this->scheme;
188
-	}
189
-
190
-	/**
191
-	 * @param string $scheme
192
-	 * @return WebSocketConfig
193
-	 */
194
-	public function setScheme(string $scheme): WebSocketConfig
195
-	{
196
-		$this->scheme = $scheme;
197
-		return $this;
198
-	}
199
-
200
-	/**
201
-	 * @return string
202
-	 */
203
-	public function getHost(): string
204
-	{
205
-		return $this->host;
206
-	}
207
-
208
-	/**
209
-	 * @param string $host
210
-	 * @return WebSocketConfig
211
-	 */
212
-	public function setHost(string $host): WebSocketConfig
213
-	{
214
-		$this->host = $host;
215
-		return $this;
216
-	}
217
-
218
-	/**
219
-	 * @return string
220
-	 */
221
-	public function getUser(): string
222
-	{
223
-		return $this->user;
224
-	}
225
-
226
-	/**
227
-	 * @param array $urlParts
228
-	 * @return WebSocketConfig
229
-	 */
230
-	public function setUser(array $urlParts): WebSocketConfig
231
-	{
232
-		$this->user = $urlParts['user'] ?? '';
233
-		return $this;
234
-	}
235
-
236
-	/**
237
-	 * @return string
238
-	 */
239
-	public function getPassword(): string
240
-	{
241
-		return $this->password;
242
-	}
243
-
244
-	/**
245
-	 * @param array $urlParts
246
-	 * @return WebSocketConfig
247
-	 */
248
-	public function setPassword(array $urlParts): WebSocketConfig
249
-	{
250
-		$this->password = $urlParts['pass'] ?? '';
251
-		return $this;
252
-	}
253
-
254
-	/**
255
-	 * @return string
256
-	 */
257
-	public function getPort(): string
258
-	{
259
-		return $this->port;
260
-	}
261
-
262
-	/**
263
-	 * @param array $urlParts
264
-	 * @return WebSocketConfig
265
-	 */
266
-	public function setPort(array $urlParts): WebSocketConfig
267
-	{
268
-		$this->port = $urlParts['port'] ?? ($this->scheme === 'wss' ? '443' : '80');
269
-		return $this;
270
-	}
271
-
272
-	/**
273
-	 * @return array
274
-	 */
275
-	public function getContextOptions(): array
276
-	{
277
-		return $this->contextOptions;
278
-	}
279
-
280
-	/**
281
-	 * @param array $contextOptions
282
-	 * @return WebSocketConfig
283
-	 */
284
-	public function setContextOptions(array $contextOptions): WebSocketConfig
285
-	{
286
-		$this->contextOptions = $contextOptions;
287
-		return $this;
288
-	}
289
-
290
-	/**
291
-	 * @param string $ip
292
-	 * @param string $port
293
-	 * @return WebSocketConfig
294
-	 */
295
-	public function setProxy(string $ip, string $port): WebSocketConfig
296
-	{
297
-		$this->hasProxy = true;
298
-		$this->proxyIp = $ip;
299
-		$this->proxyPort = $port;
300
-
301
-		return $this;
302
-	}
303
-
304
-	/**
305
-	 * Sets auth for proxy
306
-	 *
307
-	 * @param string $userName
308
-	 * @param string $password
309
-	 * @return WebSocketConfig
310
-	 */
311
-	public function setProxyAuth(string $userName, string $password): WebSocketConfig
312
-	{
313
-		$this->proxyAuth = (empty($userName) === false && empty($password) === false) ? base64_encode($userName . ':' . $password) : null;
314
-		return $this;
315
-	}
316
-
317
-	/**
318
-	 * @return bool
319
-	 */
320
-	public function hasProxy(): bool
321
-	{
322
-		return $this->hasProxy;
323
-	}
324
-
325
-	/**
326
-	 * @return string|null
327
-	 */
328
-	public function getProxyIp(): ?string
329
-	{
330
-		return $this->proxyIp;
331
-	}
332
-
333
-	/**
334
-	 * @return string|null
335
-	 */
336
-	public function getProxyPort(): ?string
337
-	{
338
-		return $this->proxyPort;
339
-	}
340
-
341
-	/**
342
-	 * @return string|null
343
-	 */
344
-	public function getProxyAuth(): ?string
345
-	{
346
-		return $this->proxyAuth;
347
-	}
17
+    /**
18
+     * @var string
19
+     */
20
+    private string $scheme;
21
+
22
+    /**
23
+     * @var string
24
+     */
25
+    private string $host;
26
+
27
+    /**
28
+     * @var string
29
+     */
30
+    private string $user;
31
+
32
+    /**
33
+     * @var string
34
+     */
35
+    private string $password;
36
+
37
+    /**
38
+     * @var string
39
+     */
40
+    private string $port;
41
+
42
+    /**
43
+     * @var int
44
+     */
45
+    private int $timeout = WscCommonsContract::DEFAULT_TIMEOUT;
46
+
47
+    /**
48
+     * @var array
49
+     */
50
+    private array $headers = [];
51
+
52
+    /**
53
+     * @var int
54
+     */
55
+    private int $fragmentSize = WscCommonsContract::DEFAULT_FRAGMENT_SIZE;
56
+
57
+    /**
58
+     * @var null|resource
59
+     */
60
+    private $context;
61
+
62
+    /**
63
+     * @var bool
64
+     */
65
+    private bool $hasProxy = false;
66
+
67
+    /**
68
+     * @var string
69
+     */
70
+    private string $proxyIp;
71
+
72
+    /**
73
+     * @var string
74
+     */
75
+    private string $proxyPort;
76
+
77
+    /**
78
+     * @var string|null
79
+     */
80
+    private ?string $proxyAuth;
81
+
82
+    /**
83
+     * @var array
84
+     */
85
+    private array $contextOptions = [];
86
+
87
+    /**
88
+     * @var int
89
+     */
90
+    private int $pingInterval = WscCommonsContract::DEFAULT_PING_INTERVAL;
91
+
92
+    /**
93
+     * @return int
94
+     */
95
+    public function getTimeout(): int
96
+    {
97
+        return $this->timeout;
98
+    }
99
+
100
+    /**
101
+     * @param int $timeout
102
+     * @return WebSocketConfig
103
+     */
104
+    public function setTimeout(int $timeout): WebSocketConfig
105
+    {
106
+        $this->timeout = $timeout;
107
+        return $this;
108
+    }
109
+
110
+    /**
111
+     * @param int $int
112
+     * @return $this
113
+     */
114
+    public function setPingInterval(int $int): WebSocketConfig
115
+    {
116
+        $this->pingInterval = $int;
117
+        return $this;
118
+    }
119
+
120
+    /**
121
+     * @return int
122
+     */
123
+    public function getPingInterval(): int
124
+    {
125
+        return $this->pingInterval;
126
+    }
127
+
128
+    /**
129
+     * @return array
130
+     */
131
+    public function getHeaders(): array
132
+    {
133
+        return $this->headers;
134
+    }
135
+
136
+    /**
137
+     * @param array $headers
138
+     * @return WebSocketConfig
139
+     */
140
+    public function setHeaders(array $headers): WebSocketConfig
141
+    {
142
+        $this->headers = $headers;
143
+        return $this;
144
+    }
145
+
146
+    /**
147
+     * @return int
148
+     */
149
+    public function getFragmentSize(): int
150
+    {
151
+        return $this->fragmentSize;
152
+    }
153
+
154
+    /**
155
+     * @param int $fragmentSize
156
+     * @return WebSocketConfig
157
+     */
158
+    public function setFragmentSize(int $fragmentSize): WebSocketConfig
159
+    {
160
+        $this->fragmentSize = $fragmentSize;
161
+        return $this;
162
+    }
163
+
164
+    /**
165
+     * @return mixed
166
+     */
167
+    public function getContext(): mixed
168
+    {
169
+        return $this->context;
170
+    }
171
+
172
+    /**
173
+     * @param mixed $context
174
+     * @return WebSocketConfig
175
+     */
176
+    public function setContext(mixed $context): WebSocketConfig
177
+    {
178
+        $this->context = $context;
179
+        return $this;
180
+    }
181
+
182
+    /**
183
+     * @return mixed
184
+     */
185
+    public function getScheme(): string
186
+    {
187
+        return $this->scheme;
188
+    }
189
+
190
+    /**
191
+     * @param string $scheme
192
+     * @return WebSocketConfig
193
+     */
194
+    public function setScheme(string $scheme): WebSocketConfig
195
+    {
196
+        $this->scheme = $scheme;
197
+        return $this;
198
+    }
199
+
200
+    /**
201
+     * @return string
202
+     */
203
+    public function getHost(): string
204
+    {
205
+        return $this->host;
206
+    }
207
+
208
+    /**
209
+     * @param string $host
210
+     * @return WebSocketConfig
211
+     */
212
+    public function setHost(string $host): WebSocketConfig
213
+    {
214
+        $this->host = $host;
215
+        return $this;
216
+    }
217
+
218
+    /**
219
+     * @return string
220
+     */
221
+    public function getUser(): string
222
+    {
223
+        return $this->user;
224
+    }
225
+
226
+    /**
227
+     * @param array $urlParts
228
+     * @return WebSocketConfig
229
+     */
230
+    public function setUser(array $urlParts): WebSocketConfig
231
+    {
232
+        $this->user = $urlParts['user'] ?? '';
233
+        return $this;
234
+    }
235
+
236
+    /**
237
+     * @return string
238
+     */
239
+    public function getPassword(): string
240
+    {
241
+        return $this->password;
242
+    }
243
+
244
+    /**
245
+     * @param array $urlParts
246
+     * @return WebSocketConfig
247
+     */
248
+    public function setPassword(array $urlParts): WebSocketConfig
249
+    {
250
+        $this->password = $urlParts['pass'] ?? '';
251
+        return $this;
252
+    }
253
+
254
+    /**
255
+     * @return string
256
+     */
257
+    public function getPort(): string
258
+    {
259
+        return $this->port;
260
+    }
261
+
262
+    /**
263
+     * @param array $urlParts
264
+     * @return WebSocketConfig
265
+     */
266
+    public function setPort(array $urlParts): WebSocketConfig
267
+    {
268
+        $this->port = $urlParts['port'] ?? ($this->scheme === 'wss' ? '443' : '80');
269
+        return $this;
270
+    }
271
+
272
+    /**
273
+     * @return array
274
+     */
275
+    public function getContextOptions(): array
276
+    {
277
+        return $this->contextOptions;
278
+    }
279
+
280
+    /**
281
+     * @param array $contextOptions
282
+     * @return WebSocketConfig
283
+     */
284
+    public function setContextOptions(array $contextOptions): WebSocketConfig
285
+    {
286
+        $this->contextOptions = $contextOptions;
287
+        return $this;
288
+    }
289
+
290
+    /**
291
+     * @param string $ip
292
+     * @param string $port
293
+     * @return WebSocketConfig
294
+     */
295
+    public function setProxy(string $ip, string $port): WebSocketConfig
296
+    {
297
+        $this->hasProxy = true;
298
+        $this->proxyIp = $ip;
299
+        $this->proxyPort = $port;
300
+
301
+        return $this;
302
+    }
303
+
304
+    /**
305
+     * Sets auth for proxy
306
+     *
307
+     * @param string $userName
308
+     * @param string $password
309
+     * @return WebSocketConfig
310
+     */
311
+    public function setProxyAuth(string $userName, string $password): WebSocketConfig
312
+    {
313
+        $this->proxyAuth = (empty($userName) === false && empty($password) === false) ? base64_encode($userName . ':' . $password) : null;
314
+        return $this;
315
+    }
316
+
317
+    /**
318
+     * @return bool
319
+     */
320
+    public function hasProxy(): bool
321
+    {
322
+        return $this->hasProxy;
323
+    }
324
+
325
+    /**
326
+     * @return string|null
327
+     */
328
+    public function getProxyIp(): ?string
329
+    {
330
+        return $this->proxyIp;
331
+    }
332
+
333
+    /**
334
+     * @return string|null
335
+     */
336
+    public function getProxyPort(): ?string
337
+    {
338
+        return $this->proxyPort;
339
+    }
340
+
341
+    /**
342
+     * @return string|null
343
+     */
344
+    public function getProxyAuth(): ?string
345
+    {
346
+        return $this->proxyAuth;
347
+    }
348 348
 
349 349
 }
Please login to merge, or discard this patch.