From 3e035431205552990c358bcc6388213e780a6cf7 Mon Sep 17 00:00:00 2001 From: OleksandrProtsiuk Date: Wed, 24 Sep 2025 16:25:55 +0300 Subject: [PATCH 1/6] composer.json updated --- composer.json | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index 33935ac..618d67e 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { - "name": "gos/web-socket-bundle", + "name": "oroinc/web-socket-bundle", "type": "symfony-bundle", - "description": "Symfony Web Socket Bundle", + "description": "A fork of Symfony Web Socket Bundle that enables PHP 8.4 and Symfony 7 support", "keywords": ["Web Socket Bundle", "Websocket", "WAMP", "IO", "Ratchet"], "homepage": "https://github.com/GeniusesOfSymfony/WebSocketBundle", "license": "MIT", @@ -16,25 +16,24 @@ } ], "require": { - "php": "^7.4 || ^8.0", - "cboden/ratchet": "^0.4.4", - "gos/pubsub-router-bundle": "^2.2", - "gos/websocket-client": "^1.1", + "php": "^8.4", + "cboden/ratchet": "^0.4", + "oroinc/pubsub-router-bundle": "2.9.x-dev", "psr/log": "^1.1 || ^2.0 || ^3.0", "react/event-loop": "^1.2", "react/socket": "^1.9", - "symfony/config": "^4.4 || ^5.4 || ^6.0", - "symfony/console": "^4.4 || ^5.4 || ^6.0", - "symfony/dependency-injection": "^4.4 || ^5.4 || ^6.0", + "symfony/config": "^6.0 || ^7.0", + "symfony/console": "^6.0 || ^7.0", + "symfony/dependency-injection": "^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/event-dispatcher": "^4.4 || ^5.4 || ^6.0", - "symfony/http-foundation": "^4.4 || ^5.4 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.4 || ^6.0", + "symfony/event-dispatcher": "^6.0 || ^7.0", + "symfony/http-foundation": "^6.0 || ^7.0", + "symfony/http-kernel": "^6.0 || ^7.0", "symfony/polyfill-php80": "^1.15", - "symfony/security-core": "^4.4 || ^5.4 || ^6.0", - "symfony/serializer": "^4.4 || ^5.4 || ^6.0", - "symfony/string": "^5.4 || ^6.0", - "symfony/yaml": "^4.4 || ^5.4 || ^6.0" + "symfony/security-core": "^6.0 || ^7.0", + "symfony/serializer": "^6.0 || ^7.0", + "symfony/string": "^6.0 || ^7.0", + "symfony/yaml": "^6.0 || ^7.0" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", @@ -45,12 +44,12 @@ "phpstan/phpstan-phpunit": "1.1.1", "phpstan/phpstan-symfony": "1.2.13", "phpunit/phpunit": "^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/options-resolver": "^4.4 || ^5.4 || ^6.0", - "symfony/phpunit-bridge": "^5.4 || ^6.0", - "symfony/stopwatch": "^4.4 || ^5.4 || ^6.0", - "symfony/twig-bundle": "^4.4 || ^5.4 || ^6.0", - "symfony/web-profiler-bundle": "^4.4 || ^5.4 || ^6.0" + "symfony/cache": "^6.0 || ^7.0", + "symfony/options-resolver": "^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.0 || ^7.0", + "symfony/stopwatch": "^6.0 || ^7.0", + "symfony/twig-bundle": "^6.0 || ^7.0", + "symfony/web-profiler-bundle": "^6.0 || ^7.0" }, "conflict": { "doctrine/cache": "<1.11", From 3e100f85ad93dd0e92ac777ecaa65e22facf1441 Mon Sep 17 00:00:00 2001 From: OleksandrProtsiuk Date: Wed, 24 Sep 2025 16:37:05 +0300 Subject: [PATCH 2/6] WebSocket client from gos/websocket-client was added --- .../GosWebSocketExtension.php | 8 +- src/Exception/BadResponseException.php | 24 + src/Exception/WebSocketException.php | 24 + src/Pusher/Wamp/WampConnectionFactory.php | 6 +- .../Wamp/WampConnectionFactoryInterface.php | 4 +- src/Pusher/Wamp/WampPusher.php | 2 +- src/Wamp/Client.php | 428 ++++++++++++++++++ src/Wamp/ClientFactory.php | 81 ++++ src/Wamp/ClientFactoryInterface.php | 25 + src/Wamp/ClientInterface.php | 72 +++ src/Wamp/PayloadGenerator.php | 74 +++ src/Wamp/PayloadGeneratorInterface.php | 27 ++ src/Wamp/Protocol.php | 37 ++ src/Wamp/WebsocketPayload.php | 198 ++++++++ .../GosWebSocketExtensionTest.php | 4 +- .../Pusher/Wamp/WampConnectionFactoryTest.php | 2 +- tests/Pusher/Wamp/WampPusherTest.php | 2 +- 17 files changed, 1003 insertions(+), 15 deletions(-) create mode 100644 src/Exception/BadResponseException.php create mode 100644 src/Exception/WebSocketException.php create mode 100644 src/Wamp/Client.php create mode 100644 src/Wamp/ClientFactory.php create mode 100644 src/Wamp/ClientFactoryInterface.php create mode 100644 src/Wamp/ClientInterface.php create mode 100644 src/Wamp/PayloadGenerator.php create mode 100644 src/Wamp/PayloadGeneratorInterface.php create mode 100644 src/Wamp/Protocol.php create mode 100644 src/Wamp/WebsocketPayload.php diff --git a/src/DependencyInjection/GosWebSocketExtension.php b/src/DependencyInjection/GosWebSocketExtension.php index b8fc505..0cb2b1a 100644 --- a/src/DependencyInjection/GosWebSocketExtension.php +++ b/src/DependencyInjection/GosWebSocketExtension.php @@ -16,10 +16,10 @@ use Gos\Bundle\WebSocketBundle\RPC\RpcInterface; use Gos\Bundle\WebSocketBundle\Server\Type\ServerInterface; use Gos\Bundle\WebSocketBundle\Topic\TopicInterface; -use Gos\Component\WebSocketClient\Wamp\Client; -use Gos\Component\WebSocketClient\Wamp\ClientFactory; -use Gos\Component\WebSocketClient\Wamp\ClientFactoryInterface; -use Gos\Component\WebSocketClient\Wamp\ClientInterface; +use Gos\Bundle\WebSocketBundle\Wamp\Client; +use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory; +use Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface; +use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; diff --git a/src/Exception/BadResponseException.php b/src/Exception/BadResponseException.php new file mode 100644 index 0000000..40650e3 --- /dev/null +++ b/src/Exception/BadResponseException.php @@ -0,0 +1,24 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +class BadResponseException extends WebSocketException +{ +} diff --git a/src/Exception/WebSocketException.php b/src/Exception/WebSocketException.php new file mode 100644 index 0000000..5a9d9cb --- /dev/null +++ b/src/Exception/WebSocketException.php @@ -0,0 +1,24 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +class WebSocketException extends \Exception +{ +} diff --git a/src/Pusher/Wamp/WampConnectionFactory.php b/src/Pusher/Wamp/WampConnectionFactory.php index 6d51c0c..d1cfb1b 100644 --- a/src/Pusher/Wamp/WampConnectionFactory.php +++ b/src/Pusher/Wamp/WampConnectionFactory.php @@ -2,9 +2,9 @@ namespace Gos\Bundle\WebSocketBundle\Pusher\Wamp; -use Gos\Component\WebSocketClient\Wamp\Client; -use Gos\Component\WebSocketClient\Wamp\ClientFactory; -use Gos\Component\WebSocketClient\Wamp\ClientInterface; +use Gos\Bundle\WebSocketBundle\Wamp\Client; +use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory; +use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Component\OptionsResolver\OptionsResolver; diff --git a/src/Pusher/Wamp/WampConnectionFactoryInterface.php b/src/Pusher/Wamp/WampConnectionFactoryInterface.php index 74e8195..3230ec8 100644 --- a/src/Pusher/Wamp/WampConnectionFactoryInterface.php +++ b/src/Pusher/Wamp/WampConnectionFactoryInterface.php @@ -2,9 +2,7 @@ namespace Gos\Bundle\WebSocketBundle\Pusher\Wamp; -use Gos\Component\WebSocketClient\Wamp\ClientFactoryInterface; - -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" interface is deprecated and will be removed in 4.0, use "%s" instead.', WampConnectionFactoryInterface::class, ClientFactoryInterface::class); +use Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface; /** * @deprecated to be removed in 4.0, use the Gos\Component\WebSocketClient\Wamp\ClientFactoryInterface from the gos/websocket-client package instead diff --git a/src/Pusher/Wamp/WampPusher.php b/src/Pusher/Wamp/WampPusher.php index e6b24bf..31057e2 100644 --- a/src/Pusher/Wamp/WampPusher.php +++ b/src/Pusher/Wamp/WampPusher.php @@ -5,7 +5,7 @@ use Gos\Bundle\WebSocketBundle\Pusher\AbstractPusher; use Gos\Bundle\WebSocketBundle\Pusher\Message; use Gos\Bundle\WebSocketBundle\Router\WampRouter; -use Gos\Component\WebSocketClient\Wamp\ClientInterface; +use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; use Symfony\Component\Serializer\SerializerInterface; trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', WampPusher::class); diff --git a/src/Wamp/Client.php b/src/Wamp/Client.php new file mode 100644 index 0000000..ab9defc --- /dev/null +++ b/src/Wamp/Client.php @@ -0,0 +1,428 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class Client implements ClientInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var bool + */ + private $connected = false; + + /** + * @var string + */ + private $endpoint; + + /** + * @var string|null + */ + private $target; + + /** + * @var resource|null + */ + private $socket; + + /** + * @var string|null + */ + private $sessionId; + + /** + * @var string + */ + private $serverHost; + + /** + * @var int + */ + private $serverPort; + + /** + * @var bool + */ + private $secured = false; + + /** + * @var string|null + */ + private $origin; + + /** + * @var PayloadGeneratorInterface + */ + private $payloadGenerator; + + public function __construct(string $host, int $port, bool $secured = false, ?string $origin = null, ?PayloadGeneratorInterface $payloadGenerator = null) + { + $this->serverHost = $host; + $this->serverPort = $port; + $this->secured = $secured; + $this->origin = null !== $origin ? $origin : $host; + $this->payloadGenerator = $payloadGenerator ?: new PayloadGenerator(); + + $this->endpoint = sprintf( + '%s://%s:%s', + $secured ? 'ssl' : 'tcp', + $host, + $port + ); + } + + /** + * @return string The session identifier for the connection + * + * @throws BadResponseException if a response could not be received from the websocket server + * @throws WebsocketException if the target URI is invalid + */ + public function connect(string $target = '/'): string + { + if ($this->connected) { + return $this->sessionId; + } + + $socket = @stream_socket_client($this->endpoint, $errno, $errstr); + + if (false === $socket) { + if (null !== $this->logger) { + $this->logger->error('Could not open socket.', ['errno' => $errno, 'errstr' => $errstr]); + } + + throw new BadResponseException('Could not open socket. Reason: '.$errstr, $errno); + } + + $this->target = $target; + $this->socket = $socket; + + $this->verifyResponse($this->upgradeProtocol($this->target)); + + $payload = json_decode($this->read()); + + if (false === $payload) { + throw new BadResponseException('WAMP Server sent an invalid payload.'); + } + + if (Protocol::MSG_WELCOME !== $payload[0]) { + if (null !== $this->logger) { + $this->logger->error('WAMP Server did not send a welcome message.', ['payload' => $payload]); + } + + throw new BadResponseException('WAMP Server did not send a welcome message.'); + } + + $this->connected = true; + + return $this->sessionId = $payload[1]; + } + + /** + * @return string|false Response body from the request or boolean false on failure + * + * @throws WebsocketException if the target URI is invalid + */ + private function upgradeProtocol(string $target) + { + $key = $this->generateKey(); + + if (false === strpos($target, '/')) { + if (null !== $this->logger) { + $this->logger->error('Invalid target path for WAMP server.', ['target' => $target]); + } + + throw new WebsocketException('WAMP server target must contain a "/"'); + } + + $protocol = $this->secured ? 'wss' : 'ws'; + + $out = "GET {$protocol}://{$this->serverHost}:{$this->serverPort}{$target} HTTP/1.1\r\n"; + $out .= "Host: {$this->serverHost}:{$this->serverPort}\r\n"; + $out .= "Pragma: no-cache\r\n"; + $out .= "Cache-Control: no-cache\r\n"; + $out .= "Upgrade: WebSocket\r\n"; + $out .= "Connection: Upgrade\r\n"; + $out .= "Sec-WebSocket-Key: $key\r\n"; + $out .= "Sec-WebSocket-Protocol: wamp\r\n"; + $out .= "Sec-WebSocket-Version: 13\r\n"; + $out .= "Origin: {$this->origin}\r\n\r\n"; + + fwrite($this->socket, $out); + + return fgets($this->socket); + } + + /** + * @param string|false $response Response body from the upgrade request or boolean false on failure + * + * @throws BadResponseException if an invalid response was received + */ + private function verifyResponse($response): void + { + if (false === $response) { + if (null !== $this->logger) { + $this->logger->error('WAMP Server did not respond properly'); + } + + throw new BadResponseException('WAMP Server did not respond properly'); + } + + $responseStatus = substr($response, 0, 12); + + if ('HTTP/1.1 101' !== $responseStatus) { + if (null !== $this->logger) { + $this->logger->error('Unexpected HTTP response from WAMP server.', ['response' => $response]); + } + + throw new BadResponseException(sprintf('Unexpected response status. Expected "HTTP/1.1 101", got "%s".', $responseStatus)); + } + } + + /** + * Read the buffer and return the oldest event in stack. + * + * @see https://tools.ietf.org/html/rfc6455#section-5.2 + * + * @throws BadResponseException if the buffer could not be read + */ + private function read(): string + { + $streamBody = stream_get_contents($this->socket, stream_get_meta_data($this->socket)['unread_bytes']); + + if (false === $streamBody) { + if (null !== $this->logger) { + $this->logger->error('The stream buffer could not be read.', ['error' => error_get_last()]); + } + + throw new BadResponseException('The stream buffer could not be read.'); + } + + $startPos = strpos($streamBody, '['); + $endPos = strpos($streamBody, ']'); + + if (false === $startPos || false === $endPos) { + if (null !== $this->logger) { + $this->logger->error('Could not extract response body from stream.', ['body' => $streamBody]); + } + + throw new BadResponseException('Could not extract response body from stream.'); + } + + return substr( + $streamBody, + $startPos, + $endPos + ); + } + + /** + * @throws WebsocketException if the connection could not be disconnected cleanly + */ + public function disconnect(): bool + { + if (false === $this->connected) { + return true; + } + + if (null === $this->socket) { + return true; + } + + $this->send($this->payloadGenerator->generateClosePayload(), WebsocketPayload::OPCODE_CLOSE); + + $firstByte = fread($this->socket, 1); + + if (false === $firstByte) { + if (null !== $this->logger) { + $this->logger->error('Could not extract the payload from the buffer.', ['error' => error_get_last()]); + } + + throw new WebsocketException('Could not extract the payload from the buffer.'); + } + + /** @phpstan-var int<0, 255> $payloadLength */ + $payloadLength = \ord($firstByte); + $payload = fread($this->socket, $payloadLength); + + if (false === $payload) { + if (null !== $this->logger) { + $this->logger->error('Could not extract the payload from the buffer.', ['error' => error_get_last()]); + } + + throw new WebsocketException('Could not extract the payload from the buffer.'); + } + + if ($payloadLength >= 2) { + $bin = $payload[0].$payload[1]; + $status = bindec(sprintf('%08b%08b', \ord($payload[0]), \ord($payload[1]))); + + $this->send($bin.'Close acknowledged: '.$status, WebsocketPayload::OPCODE_CLOSE); + } + + fclose($this->socket); + $this->connected = false; + + return true; + } + + /** + * @param mixed $data Any JSON encodable data + * + * @throws WebsocketException if the data cannot be encoded properly + */ + private function send($data, int $opcode = WebsocketPayload::OPCODE_TEXT): void + { + if (\is_array($data)) { + $payload = json_encode($data); + + if (false === $payload) { + throw new WebsocketException('The data could not be encoded: '.json_last_error_msg()); + } + } elseif (is_scalar($data)) { + $payload = $data; + } else { + throw new WebsocketException('The data must be an array or a scalar value.'); + } + + $encoded = $this->payloadGenerator->encode( + (new WebsocketPayload()) + ->setOpcode($opcode) + ->setMask(0x1) + ->setPayload($payload) + ); + + // Check if the connection was reset, if so try to reconnect + if (false === @fwrite($this->socket, $encoded)) { + $this->connected = false; + $this->connect($this->target); + + fwrite($this->socket, $encoded); + } + } + + /** + * Establish a prefix on server. + * + * @see http://wamp.ws/spec#prefix_message + */ + public function prefix(string $prefix, string $uri): void + { + if (null !== $this->logger) { + $this->logger->info(sprintf('Establishing prefix "%s" for URI "%s"', $prefix, $uri)); + } + + $this->send([Protocol::MSG_PREFIX, $prefix, $uri]); + } + + /** + * Call a procedure on server. + * + * @see http://wamp.ws/spec#call_message + * + * @param array|mixed $args Arguments for the message either as an array or variadic set of parameters + */ + public function call(string $procUri, $args): void + { + if (!\is_array($args)) { + $args = \func_get_args(); + array_shift($args); + } + + if (null !== $this->logger) { + $this->logger->info( + sprintf('Websocket client calling %s', $procUri), + [ + 'callArguments' => $args, + ] + ); + } + + $this->send( + array_merge( + [Protocol::MSG_CALL, uniqid('', true), $procUri], + $args + ) + ); + } + + /** + * The client will send an event to all clients connected to the server who have subscribed to the topicURI. + * + * @see http://wamp.ws/spec#publish_message + * + * @param string[] $exclude + * @param string[] $eligible + */ + public function publish(string $topicUri, string $payload, array $exclude = [], array $eligible = []): void + { + if (null !== $this->logger) { + $this->logger->info( + sprintf('Websocket client publishing to %s', $topicUri), + [ + 'payload' => $payload, + 'excludedIds' => $exclude, + 'eligibleIds' => $eligible, + ] + ); + } + + $this->send([Protocol::MSG_PUBLISH, $topicUri, $payload, $exclude, $eligible]); + } + + /** + * Subscribers receive PubSub events published by subscribers via the EVENT message. The EVENT message contains the topicURI, the topic under which the event was published, and event, the PubSub event payload. + */ + public function event(string $topicUri, string $payload): void + { + if (null !== $this->logger) { + $this->logger->info( + sprintf('Websocket client sending event to %s', $topicUri), + [ + 'payload' => $payload, + ] + ); + } + + $this->send([Protocol::MSG_EVENT, $topicUri, $payload]); + } + + private function generateKey(int $length = 16): string + { + $c = 0; + $tmp = ''; + + while ($c++ * 16 < $length) { + $tmp .= md5((string) mt_rand(), true); + } + + return base64_encode(substr($tmp, 0, $length)); + } + + public function isConnected(): bool + { + return $this->connected; + } +} diff --git a/src/Wamp/ClientFactory.php b/src/Wamp/ClientFactory.php new file mode 100644 index 0000000..f0f1b24 --- /dev/null +++ b/src/Wamp/ClientFactory.php @@ -0,0 +1,81 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class ClientFactory implements ClientFactoryInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var array + */ + private $config; + + public function __construct(array $config) + { + $this->config = $this->resolveConfig($config); + } + + public function createConnection(): ClientInterface + { + $client = new Client( + $this->config['host'], + $this->config['port'], + $this->config['ssl'], + $this->config['origin'] + ); + + if (null !== $this->logger) { + $client->setLogger($this->logger); + } + + return $client; + } + + private function resolveConfig(array $config): array + { + $resolver = new OptionsResolver(); + + $resolver->setRequired( + [ + 'host', + 'port', + ] + ); + + $resolver->setDefaults( + [ + 'ssl' => false, + 'origin' => null, + ] + ); + + $resolver->setAllowedTypes('host', 'string'); + $resolver->setAllowedTypes('port', ['string', 'integer']); + $resolver->setAllowedTypes('ssl', 'boolean'); + $resolver->setAllowedTypes('origin', ['string', 'null']); + + return $resolver->resolve($config); + } +} diff --git a/src/Wamp/ClientFactoryInterface.php b/src/Wamp/ClientFactoryInterface.php new file mode 100644 index 0000000..27c30f2 --- /dev/null +++ b/src/Wamp/ClientFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +interface ClientFactoryInterface +{ + public function createConnection(): ClientInterface; +} diff --git a/src/Wamp/ClientInterface.php b/src/Wamp/ClientInterface.php new file mode 100644 index 0000000..ae6ae24 --- /dev/null +++ b/src/Wamp/ClientInterface.php @@ -0,0 +1,72 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +interface ClientInterface +{ + /** + * @return string The session identifier for the connection + * + * @throws BadResponseException if a response could not be received from the websocket server + * @throws WebsocketException if the target URI is invalid + */ + public function connect(string $target = '/'): string; + + /** + * @throws WebsocketException if the connection could not be disconnected cleanly + */ + public function disconnect(): bool; + + public function isConnected(): bool; + + /** + * Establish a prefix on server. + * + * @see http://wamp.ws/spec#prefix_message + */ + public function prefix(string $prefix, string $uri): void; + + /** + * Call a procedure on server. + * + * @see http://wamp.ws/spec#call_message + * + * @param array|mixed $args Arguments for the message either as an array or variadic set of parameters + */ + public function call(string $procUri, $args): void; + + /** + * The client will send an event to all clients connected to the server who have subscribed to the topicURI. + * + * @see http://wamp.ws/spec#publish_message + * + * @param string[] $exclude + * @param string[] $eligible + */ + public function publish(string $topicUri, string $payload, array $exclude = [], array $eligible = []): void; + + /** + * Subscribers receive PubSub events published by subscribers via the EVENT message. The EVENT message contains the topicURI, the topic under which the event was published, and event, the PubSub event payload. + */ + public function event(string $topicUri, string $payload): void; +} diff --git a/src/Wamp/PayloadGenerator.php b/src/Wamp/PayloadGenerator.php new file mode 100644 index 0000000..8df4984 --- /dev/null +++ b/src/Wamp/PayloadGenerator.php @@ -0,0 +1,74 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class PayloadGenerator implements PayloadGeneratorInterface +{ + public function encode(WebsocketPayload $websocketPayload): string + { + $payload = $websocketPayload->getFin() << 1 | $websocketPayload->getRsv1(); + $payload = $payload << 1 | $websocketPayload->getRsv2(); + $payload = $payload << 1 | $websocketPayload->getRsv3(); + $payload = $payload << 4 | $websocketPayload->getOpcode(); + $payload = $payload << 1 | $websocketPayload->getMask(); + + if ($websocketPayload->getLength() <= 125) { + $payload = $payload << 7 | $websocketPayload->getLength(); + $payload = pack('n', $payload); + } elseif ($websocketPayload->getLength() <= 0xffff) { + $payload = $payload << 7 | 126; + $payload = pack('n', $payload).pack('n*', $websocketPayload->getLength()); + } else { + $payload = $payload << 7 | 127; + $payload = pack('n', $payload).pack('NN', ($websocketPayload->getLength() & 0xffffffff00000000) >> 32, $websocketPayload->getLength() & 0x00000000ffffffff); + } + + if (0x1 == $websocketPayload->getMask()) { + $payload .= $websocketPayload->getMaskKey(); + $data = $this->maskData($websocketPayload->getPayload(), $websocketPayload->getMaskKey()); + } else { + $data = $websocketPayload->getPayload(); + } + + return $payload.$data; + } + + public function generateClosePayload(): string + { + $str = ''; + + foreach (str_split(sprintf('%016b', 1000), 8) as $binstr) { + $str .= \chr((int) bindec($binstr)); + } + + return $str.'ttfn'; + } + + private function maskData(?string $data, ?string $key): string + { + $masked = ''; + + for ($i = 0; $i < \strlen($data); ++$i) { + $masked .= $data[$i] ^ $key[$i % 4]; + } + + return $masked; + } +} diff --git a/src/Wamp/PayloadGeneratorInterface.php b/src/Wamp/PayloadGeneratorInterface.php new file mode 100644 index 0000000..292dca9 --- /dev/null +++ b/src/Wamp/PayloadGeneratorInterface.php @@ -0,0 +1,27 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +interface PayloadGeneratorInterface +{ + public function encode(WebsocketPayload $websocketPayload): string; + + public function generateClosePayload(): string; +} diff --git a/src/Wamp/Protocol.php b/src/Wamp/Protocol.php new file mode 100644 index 0000000..8fd8098 --- /dev/null +++ b/src/Wamp/Protocol.php @@ -0,0 +1,37 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class Protocol +{ + public const MSG_WELCOME = 0; + public const MSG_PREFIX = 1; + public const MSG_CALL = 2; + public const MSG_CALL_RESULT = 3; + public const MSG_CALL_ERROR = 4; + public const MSG_SUBSCRIBE = 5; + public const MSG_UNSUBSCRIBE = 6; + public const MSG_PUBLISH = 7; + public const MSG_EVENT = 8; + + private function __construct() + { + } +} diff --git a/src/Wamp/WebsocketPayload.php b/src/Wamp/WebsocketPayload.php new file mode 100644 index 0000000..6e8f383 --- /dev/null +++ b/src/Wamp/WebsocketPayload.php @@ -0,0 +1,198 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class WebsocketPayload +{ + public const OPCODE_CONTINUE = 0x0; + public const OPCODE_TEXT = 0x1; + public const OPCODE_BINARY = 0x2; + public const OPCODE_NON_CONTROL_RESERVED_1 = 0x3; + public const OPCODE_NON_CONTROL_RESERVED_2 = 0x4; + public const OPCODE_NON_CONTROL_RESERVED_3 = 0x5; + public const OPCODE_NON_CONTROL_RESERVED_4 = 0x6; + public const OPCODE_NON_CONTROL_RESERVED_5 = 0x7; + public const OPCODE_CLOSE = 0x8; + public const OPCODE_PING = 0x9; + public const OPCODE_PONG = 0xA; + public const OPCODE_CONTROL_RESERVED_1 = 0xB; + public const OPCODE_CONTROL_RESERVED_2 = 0xC; + public const OPCODE_CONTROL_RESERVED_3 = 0xD; + public const OPCODE_CONTROL_RESERVED_4 = 0xE; + public const OPCODE_CONTROL_RESERVED_5 = 0xF; + + /** + * @var int + */ + private $fin = 0x1; + + /** + * @var int + */ + private $rsv1 = 0x0; + + /** + * @var int + */ + private $rsv2 = 0x0; + + /** + * @var int + */ + private $rsv3 = 0x0; + + /** + * @var int|null + */ + private $opcode; + + /** + * @var int + */ + private $mask = 0x0; + + /** + * @var string + */ + private $maskKey; + + /** + * @var mixed + */ + private $payload; + + public function setFin(int $fin): self + { + $this->fin = $fin; + + return $this; + } + + public function getFin(): int + { + return $this->fin; + } + + public function setRsv1(int $rsv1): self + { + $this->rsv1 = $rsv1; + + return $this; + } + + public function getRsv1(): int + { + return $this->rsv1; + } + + public function setRsv2(int $rsv2): self + { + $this->rsv2 = $rsv2; + + return $this; + } + + public function getRsv2(): int + { + return $this->rsv2; + } + + public function setRsv3(int $rsv3): self + { + $this->rsv3 = $rsv3; + + return $this; + } + + public function getRsv3(): int + { + return $this->rsv3; + } + + public function setOpcode(?int $opcode): self + { + $this->opcode = $opcode; + + return $this; + } + + public function getOpcode(): ?int + { + return $this->opcode; + } + + public function setMask(int $mask): self + { + $this->mask = $mask; + + if (true == $this->mask) { + $this->generateMaskKey(); + } + + return $this; + } + + public function getMask(): int + { + return $this->mask; + } + + public function getLength(): int + { + return \strlen($this->getPayload()); + } + + public function setMaskKey(string $maskKey): self + { + $this->maskKey = $maskKey; + + return $this; + } + + public function getMaskKey(): ?string + { + return $this->maskKey; + } + + /** + * @param mixed $payload + */ + public function setPayload($payload): self + { + $this->payload = $payload; + + return $this; + } + + /** + * @return mixed + */ + public function getPayload() + { + return $this->payload; + } + + public function generateMaskKey(): string + { + $this->setMaskKey(random_bytes(4)); + + return $this->getMaskKey(); + } +} diff --git a/tests/DependencyInjection/GosWebSocketExtensionTest.php b/tests/DependencyInjection/GosWebSocketExtensionTest.php index 4b7b6bc..303b175 100644 --- a/tests/DependencyInjection/GosWebSocketExtensionTest.php +++ b/tests/DependencyInjection/GosWebSocketExtensionTest.php @@ -11,8 +11,8 @@ use Gos\Bundle\WebSocketBundle\GosWebSocketBundle; use Gos\Bundle\WebSocketBundle\Pusher\Amqp\AmqpConnectionFactory; use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampConnectionFactory; -use Gos\Component\WebSocketClient\Wamp\Client; -use Gos\Component\WebSocketClient\Wamp\ClientFactory; +use Gos\Bundle\WebSocketBundle\Wamp\Client; +use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Exception\RuntimeException; diff --git a/tests/Pusher/Wamp/WampConnectionFactoryTest.php b/tests/Pusher/Wamp/WampConnectionFactoryTest.php index fa3aaea..210cbb2 100644 --- a/tests/Pusher/Wamp/WampConnectionFactoryTest.php +++ b/tests/Pusher/Wamp/WampConnectionFactoryTest.php @@ -3,7 +3,7 @@ namespace Gos\Bundle\WebSocketBundle\Tests\Pusher\Wamp; use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampConnectionFactory; -use Gos\Component\WebSocketClient\Wamp\ClientInterface; +use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; use PHPUnit\Framework\TestCase; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; diff --git a/tests/Pusher/Wamp/WampPusherTest.php b/tests/Pusher/Wamp/WampPusherTest.php index 0b6ac63..9f9b723 100644 --- a/tests/Pusher/Wamp/WampPusherTest.php +++ b/tests/Pusher/Wamp/WampPusherTest.php @@ -6,7 +6,7 @@ use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampConnectionFactoryInterface; use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampPusher; use Gos\Bundle\WebSocketBundle\Router\WampRouter; -use Gos\Component\WebSocketClient\Wamp\ClientInterface; +use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\SerializerInterface; From 1a8237903949277eeb8208a02d21601d57ac29c7 Mon Sep 17 00:00:00 2001 From: OleksandrProtsiuk Date: Wed, 24 Sep 2025 16:39:18 +0300 Subject: [PATCH 3/6] Removed trigger_deprecation calls --- src/Authentication/ConnectionRepository.php | 8 +------- .../Auth/WebsocketAuthenticationProvider.php | 4 +--- .../WebsocketAuthenticationProviderInterface.php | 4 +--- src/Client/ClientConnection.php | 8 +------- src/Client/ClientManipulator.php | 4 +--- src/Client/ClientManipulatorInterface.php | 4 +--- src/Client/ClientStorage.php | 4 +--- src/Client/ClientStorageInterface.php | 4 +--- .../Driver/DoctrineCacheDriverDecorator.php | 4 +--- src/Client/Driver/DriverException.php | 4 +--- src/Client/Driver/DriverInterface.php | 4 +--- src/Client/Driver/InMemoryDriver.php | 4 +--- src/Client/Driver/SymfonyCacheDriverDecorator.php | 4 +--- src/Client/Exception/ClientNotFoundException.php | 4 +--- src/Client/Exception/StorageException.php | 4 +--- src/Command/WebsocketServerCommand.php | 4 ---- src/DataCollector/WebsocketDataCollector.php | 4 +--- .../CompilerPass/DataCollectorCompilerPass.php | 3 --- .../CompilerPass/PusherCompilerPass.php | 4 ---- .../ServerPushHandlerCompilerPass.php | 4 ---- src/DependencyInjection/Configuration.php | 2 +- src/Event/ClientErrorEvent.php | 15 --------------- src/Event/ClientRejectedEvent.php | 4 +--- src/Event/PushHandlerEvent.php | 4 +--- src/Event/PushHandlerFailEvent.php | 4 +--- src/Event/PushHandlerSuccessEvent.php | 4 +--- .../ClosePusherConnectionsListener.php | 4 +--- .../RegisterPushHandlersListener.php | 4 +--- src/Periodic/DoctrinePeriodicPing.php | 14 -------------- src/Periodic/PdoPeriodicPing.php | 4 ---- src/Periodic/PeriodicMemoryUsage.php | 2 -- src/Pusher/AbstractPusher.php | 4 +--- src/Pusher/AbstractServerPushHandler.php | 4 +--- src/Pusher/Amqp/AmqpConnectionFactory.php | 4 +--- .../Amqp/AmqpConnectionFactoryInterface.php | 4 +--- src/Pusher/Amqp/AmqpPusher.php | 4 +--- src/Pusher/Amqp/AmqpServerPushHandler.php | 4 +--- src/Pusher/DataCollectingPusherDecorator.php | 4 +--- .../Exception/PusherUnsupportedException.php | 4 +--- src/Pusher/Message.php | 4 +--- src/Pusher/PusherInterface.php | 4 +--- src/Pusher/PusherRegistry.php | 4 +--- src/Pusher/ServerPushHandlerInterface.php | 4 +--- src/Pusher/ServerPushHandlerRegistry.php | 4 +--- src/Pusher/Wamp/WampConnectionFactory.php | 4 +--- .../Wamp/WampConnectionFactoryInterface.php | 2 +- src/Pusher/Wamp/WampPusher.php | 4 +--- src/Server/App/Dispatcher/TopicDispatcher.php | 4 ---- src/Server/App/PushableWampServerInterface.php | 4 +--- src/Server/App/WampApplication.php | 2 -- src/Server/EntryPoint.php | 4 +--- src/Topic/PushableTopicInterface.php | 4 +--- src/Topic/TopicManager.php | 2 -- 53 files changed, 42 insertions(+), 188 deletions(-) diff --git a/src/Authentication/ConnectionRepository.php b/src/Authentication/ConnectionRepository.php index bf4f3cd..fda7360 100644 --- a/src/Authentication/ConnectionRepository.php +++ b/src/Authentication/ConnectionRepository.php @@ -118,12 +118,6 @@ public function findTokenForConnection(ConnectionInterface $connection): TokenIn */ public function getUser(ConnectionInterface $connection) { - $user = $this->findTokenForConnection($connection)->getUser(); - - if (null !== $user && !($user instanceof UserInterface)) { - trigger_deprecation('gos/web-socket-bundle', '3.14', 'Retrieving a user that is not an instance of %s is deprecated in %s().', UserInterface::class, __METHOD__); - } - - return $user; + return $this->findTokenForConnection($connection)->getUser(); } } diff --git a/src/Client/Auth/WebsocketAuthenticationProvider.php b/src/Client/Auth/WebsocketAuthenticationProvider.php index b46a210..bf0be2f 100644 --- a/src/Client/Auth/WebsocketAuthenticationProvider.php +++ b/src/Client/Auth/WebsocketAuthenticationProvider.php @@ -10,10 +10,8 @@ use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', WebsocketAuthenticationProvider::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ final class WebsocketAuthenticationProvider implements WebsocketAuthenticationProviderInterface, LoggerAwareInterface { diff --git a/src/Client/Auth/WebsocketAuthenticationProviderInterface.php b/src/Client/Auth/WebsocketAuthenticationProviderInterface.php index 3d08726..75b0a15 100644 --- a/src/Client/Auth/WebsocketAuthenticationProviderInterface.php +++ b/src/Client/Auth/WebsocketAuthenticationProviderInterface.php @@ -5,10 +5,8 @@ use Ratchet\ConnectionInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" interface is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', WebsocketAuthenticationProviderInterface::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ interface WebsocketAuthenticationProviderInterface { diff --git a/src/Client/ClientConnection.php b/src/Client/ClientConnection.php index 274c840..d6222ac 100644 --- a/src/Client/ClientConnection.php +++ b/src/Client/ClientConnection.php @@ -6,10 +6,8 @@ use ReturnTypeWillChange; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', ClientConnection::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ final class ClientConnection implements \ArrayAccess { @@ -37,8 +35,6 @@ public function getConnection(): ConnectionInterface */ public function offsetExists($offset): bool { - trigger_deprecation('gos/web-socket-bundle', '3.0', 'Accessing properties from %s as an array is deprecated and will be removed in 4.0, use the getters to access the properties.', self::class); - return \in_array($offset, ['client', 'connection'], true); } @@ -50,8 +46,6 @@ public function offsetExists($offset): bool #[ReturnTypeWillChange] public function offsetGet($offset) { - trigger_deprecation('gos/web-socket-bundle', '3.0', 'Accessing properties from %s as an array is deprecated and will be removed in 4.0, use the getters to access the properties.', self::class); - switch ($offset) { case 'client': return $this->client; diff --git a/src/Client/ClientManipulator.php b/src/Client/ClientManipulator.php index cef6dc2..f5feabe 100644 --- a/src/Client/ClientManipulator.php +++ b/src/Client/ClientManipulator.php @@ -9,10 +9,8 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', ClientManipulator::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ final class ClientManipulator implements ClientManipulatorInterface { diff --git a/src/Client/ClientManipulatorInterface.php b/src/Client/ClientManipulatorInterface.php index 2146be6..8da190d 100644 --- a/src/Client/ClientManipulatorInterface.php +++ b/src/Client/ClientManipulatorInterface.php @@ -7,10 +7,8 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" interface is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', ClientManipulatorInterface::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ interface ClientManipulatorInterface { diff --git a/src/Client/ClientStorage.php b/src/Client/ClientStorage.php index 773b9b8..c1b6ee1 100644 --- a/src/Client/ClientStorage.php +++ b/src/Client/ClientStorage.php @@ -10,10 +10,8 @@ use Ratchet\ConnectionInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', ClientStorage::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ final class ClientStorage implements ClientStorageInterface, LoggerAwareInterface { diff --git a/src/Client/ClientStorageInterface.php b/src/Client/ClientStorageInterface.php index 7f3b353..1da1da9 100644 --- a/src/Client/ClientStorageInterface.php +++ b/src/Client/ClientStorageInterface.php @@ -7,10 +7,8 @@ use Ratchet\ConnectionInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" interface is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', ClientStorageInterface::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead * * @method void removeAllClients() */ diff --git a/src/Client/Driver/DoctrineCacheDriverDecorator.php b/src/Client/Driver/DoctrineCacheDriverDecorator.php index bb8b580..7137e90 100644 --- a/src/Client/Driver/DoctrineCacheDriverDecorator.php +++ b/src/Client/Driver/DoctrineCacheDriverDecorator.php @@ -6,12 +6,10 @@ use Doctrine\Common\Cache\ClearableCache; use Symfony\Component\Cache\DoctrineProvider; -trigger_deprecation('gos/web-socket-bundle', '3.4', 'The "%s" class is deprecated and will be removed in 4.0, use the "%s" class with a "%s" instance instead.', DoctrineCacheDriverDecorator::class, SymfonyCacheDriverDecorator::class, DoctrineProvider::class); - /** * @author Johann Saunier * - * @deprecated to be removed in 4.0, use the `Gos\Bundle\WebSocketBundle\Client\Driver\SymfonyCacheDriverDecorator` with a `Symfony\Component\Cache\DoctrineProvider` instance instead + * deprecated to be removed in 4.0, use the `Gos\Bundle\WebSocketBundle\Client\Driver\SymfonyCacheDriverDecorator` with a `Symfony\Component\Cache\DoctrineProvider` instance instead */ final class DoctrineCacheDriverDecorator implements DriverInterface { diff --git a/src/Client/Driver/DriverException.php b/src/Client/Driver/DriverException.php index 54062f8..f208375 100644 --- a/src/Client/Driver/DriverException.php +++ b/src/Client/Driver/DriverException.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Client\Driver; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', DriverException::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ class DriverException extends \Exception { diff --git a/src/Client/Driver/DriverInterface.php b/src/Client/Driver/DriverInterface.php index 8736851..9f7abb2 100644 --- a/src/Client/Driver/DriverInterface.php +++ b/src/Client/Driver/DriverInterface.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Client\Driver; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" interface is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', DriverInterface::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead * * @method void clear() */ diff --git a/src/Client/Driver/InMemoryDriver.php b/src/Client/Driver/InMemoryDriver.php index ba9685a..a6a3812 100644 --- a/src/Client/Driver/InMemoryDriver.php +++ b/src/Client/Driver/InMemoryDriver.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Client\Driver; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', InMemoryDriver::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ final class InMemoryDriver implements DriverInterface { diff --git a/src/Client/Driver/SymfonyCacheDriverDecorator.php b/src/Client/Driver/SymfonyCacheDriverDecorator.php index dabc477..af52885 100644 --- a/src/Client/Driver/SymfonyCacheDriverDecorator.php +++ b/src/Client/Driver/SymfonyCacheDriverDecorator.php @@ -4,10 +4,8 @@ use Symfony\Component\Cache\Adapter\AdapterInterface; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', SymfonyCacheDriverDecorator::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ final class SymfonyCacheDriverDecorator implements DriverInterface { diff --git a/src/Client/Exception/ClientNotFoundException.php b/src/Client/Exception/ClientNotFoundException.php index 175ed9c..e994881 100644 --- a/src/Client/Exception/ClientNotFoundException.php +++ b/src/Client/Exception/ClientNotFoundException.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Client\Exception; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', ClientNotFoundException::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ class ClientNotFoundException extends StorageException { diff --git a/src/Client/Exception/StorageException.php b/src/Client/Exception/StorageException.php index 15a9056..1d44ce7 100644 --- a/src/Client/Exception/StorageException.php +++ b/src/Client/Exception/StorageException.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Client\Exception; -trigger_deprecation('gos/web-socket-bundle', '3.11', 'The "%s" class is deprecated and will be removed in 4.0, use the new websocket authentication API instead.', StorageException::class); - /** - * @deprecated to be removed in 4.0, use the new websocket authentication API instead + * deprecated to be removed in 4.0, use the new websocket authentication API instead */ class StorageException extends \Exception { diff --git a/src/Command/WebsocketServerCommand.php b/src/Command/WebsocketServerCommand.php index 261b100..7de9b79 100644 --- a/src/Command/WebsocketServerCommand.php +++ b/src/Command/WebsocketServerCommand.php @@ -34,10 +34,6 @@ public function __construct(ServerLauncherInterface $entryPoint, string $host, i { parent::__construct(); - if (null === $serverRegistry) { - trigger_deprecation('gos/web-socket-bundle', '3.12', 'Not passing the "%s" to the "%s" constructor is deprecated and will be required as of 4.0.', ServerRegistry::class, self::class); - } - $this->serverLauncher = $entryPoint; $this->port = $port; $this->host = $host; diff --git a/src/DataCollector/WebsocketDataCollector.php b/src/DataCollector/WebsocketDataCollector.php index 2eff8a3..a3ff69c 100644 --- a/src/DataCollector/WebsocketDataCollector.php +++ b/src/DataCollector/WebsocketDataCollector.php @@ -4,10 +4,8 @@ use Symfony\Component\Stopwatch\StopwatchEvent; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0.', WebsocketDataCollector::class); - /** - * @deprecated to be removed in 4.0 + * deprecated to be removed in 4.0 */ final class WebsocketDataCollector extends WebsocketCompatibilityDataCollector { diff --git a/src/DependencyInjection/CompilerPass/DataCollectorCompilerPass.php b/src/DependencyInjection/CompilerPass/DataCollectorCompilerPass.php index 0245458..2dac82a 100644 --- a/src/DependencyInjection/CompilerPass/DataCollectorCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/DataCollectorCompilerPass.php @@ -25,9 +25,6 @@ public function __construct(bool $internal = false) public function process(ContainerBuilder $container): void { - if (!$this->internal) { - trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', self::class); - } if (!$container->getParameter('kernel.debug') || !$container->hasDefinition('debug.stopwatch')) { return; diff --git a/src/DependencyInjection/CompilerPass/PusherCompilerPass.php b/src/DependencyInjection/CompilerPass/PusherCompilerPass.php index 31b14fe..0ba2a33 100644 --- a/src/DependencyInjection/CompilerPass/PusherCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/PusherCompilerPass.php @@ -27,10 +27,6 @@ public function __construct(bool $internal = false) */ public function process(ContainerBuilder $container): void { - if (!$this->internal) { - trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', self::class); - } - if ($container->hasDefinition('gos_web_socket.registry.pusher')) { $registryDefinition = $container->getDefinition('gos_web_socket.registry.pusher'); diff --git a/src/DependencyInjection/CompilerPass/ServerPushHandlerCompilerPass.php b/src/DependencyInjection/CompilerPass/ServerPushHandlerCompilerPass.php index c6cb7da..45f35d6 100644 --- a/src/DependencyInjection/CompilerPass/ServerPushHandlerCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/ServerPushHandlerCompilerPass.php @@ -27,10 +27,6 @@ public function __construct(bool $internal = false) */ public function process(ContainerBuilder $container): void { - if (!$this->internal) { - trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', self::class); - } - if ($container->hasDefinition('gos_web_socket.registry.server_push_handler')) { $registryDefinition = $container->getDefinition('gos_web_socket.registry.server_push_handler'); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 28fbac4..13b18f9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -113,7 +113,7 @@ private function addAuthenticationSection(ArrayNodeDefinition $rootNode): void ->validate() ->ifTrue(static fn (bool $enableAuthenticator): bool => !$enableAuthenticator) ->then(static function (bool $enableAuthenticator): void { - trigger_deprecation('gos/web-socket-bundle', '3.11', 'Not setting the "gos_web_socket.authentication.enable_authenticator" config option to true is deprecated.'); + // Deprecated: trigger_deprecation removed }) ->end() ->end() diff --git a/src/Event/ClientErrorEvent.php b/src/Event/ClientErrorEvent.php index faec959..224b4ee 100644 --- a/src/Event/ClientErrorEvent.php +++ b/src/Event/ClientErrorEvent.php @@ -11,13 +11,6 @@ final class ClientErrorEvent extends ClientEvent */ public function setException(\Throwable $exception): void { - trigger_deprecation( - 'gos/web-socket-bundle', - '3.3', - '%s() is deprecated and will be removed in 4.0, the Throwable will be a required constructor argument.', - __METHOD__ - ); - $this->throwable = $exception; } @@ -28,14 +21,6 @@ public function setException(\Throwable $exception): void */ public function getException() { - trigger_deprecation( - 'gos/web-socket-bundle', - '3.3', - '%s() is deprecated and will be removed in 4.0, use %s::getThrowable() instead.', - __METHOD__, - self::class - ); - return $this->getThrowable(); } diff --git a/src/Event/ClientRejectedEvent.php b/src/Event/ClientRejectedEvent.php index 6a61a31..0d72fec 100644 --- a/src/Event/ClientRejectedEvent.php +++ b/src/Event/ClientRejectedEvent.php @@ -5,11 +5,9 @@ use Psr\Http\Message\RequestInterface; use Symfony\Contracts\EventDispatcher\Event; -trigger_deprecation('gos/web-socket-bundle', '3.8', 'The "%s" class is deprecated and will be removed in 4.0, subscribe to the "%s" event instead.', ClientRejectedEvent::class, ConnectionRejectedEvent::class); - /** * @author Johann Saunier - * @deprecated to be removed in 4.0, subscribe to the `Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent` event instead + * deprecated to be removed in 4.0, subscribe to the `Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent` event instead */ final class ClientRejectedEvent extends Event { diff --git a/src/Event/PushHandlerEvent.php b/src/Event/PushHandlerEvent.php index b267feb..b0a34fa 100644 --- a/src/Event/PushHandlerEvent.php +++ b/src/Event/PushHandlerEvent.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Pusher\ServerPushHandlerInterface; use Symfony\Contracts\EventDispatcher\Event; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PushHandlerEvent::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ abstract class PushHandlerEvent extends Event { diff --git a/src/Event/PushHandlerFailEvent.php b/src/Event/PushHandlerFailEvent.php index baa82f5..ea59b69 100644 --- a/src/Event/PushHandlerFailEvent.php +++ b/src/Event/PushHandlerFailEvent.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Event; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PushHandlerFailEvent::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class PushHandlerFailEvent extends PushHandlerEvent { diff --git a/src/Event/PushHandlerSuccessEvent.php b/src/Event/PushHandlerSuccessEvent.php index 864ce94..8d8f7d3 100644 --- a/src/Event/PushHandlerSuccessEvent.php +++ b/src/Event/PushHandlerSuccessEvent.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Event; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PushHandlerSuccessEvent::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class PushHandlerSuccessEvent extends PushHandlerEvent { diff --git a/src/EventListener/ClosePusherConnectionsListener.php b/src/EventListener/ClosePusherConnectionsListener.php index cda762d..7a290ad 100644 --- a/src/EventListener/ClosePusherConnectionsListener.php +++ b/src/EventListener/ClosePusherConnectionsListener.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Pusher\PusherRegistry; use Symfony\Component\HttpKernel\Event\TerminateEvent; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', ClosePusherConnectionsListener::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class ClosePusherConnectionsListener { diff --git a/src/EventListener/RegisterPushHandlersListener.php b/src/EventListener/RegisterPushHandlersListener.php index a314751..5c614da 100644 --- a/src/EventListener/RegisterPushHandlersListener.php +++ b/src/EventListener/RegisterPushHandlersListener.php @@ -8,10 +8,8 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', RegisterPushHandlersListener::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class RegisterPushHandlersListener implements LoggerAwareInterface { diff --git a/src/Periodic/DoctrinePeriodicPing.php b/src/Periodic/DoctrinePeriodicPing.php index 437a933..90db516 100644 --- a/src/Periodic/DoctrinePeriodicPing.php +++ b/src/Periodic/DoctrinePeriodicPing.php @@ -31,16 +31,6 @@ public function __construct(object $connection, int $interval = 20) throw new \InvalidArgumentException(sprintf('The connection must be a subclass of %s or implement %s, %s does not fulfill these requirements.', Connection::class, PingableConnection::class, \get_class($connection))); } - if ($connection instanceof PingableConnection && !($connection instanceof Connection)) { - trigger_deprecation( - 'gos/web-socket-bundle', - '3.3', - 'Support for "%s" instances which are not an instance of "%s" is deprecated and will be removed in 4.0.', - PingableConnection::class, - Connection::class - ); - } - $this->connection = $connection; $this->interval = $interval; } @@ -87,15 +77,11 @@ public function getInterval(): int */ public function getTimeout(): int { - trigger_deprecation('gos/web-socket-bundle', '3.9', '%s() is deprecated and will be removed in 4.0, call %s::getInterval() instead.', __METHOD__, self::class); - return $this->getInterval(); } public function setTimeout(int $timeout): void { - trigger_deprecation('gos/web-socket-bundle', '3.9', '%s() is deprecated and will be removed in 4.0, set the timeout through the constructor instead.', __METHOD__); - $this->interval = $timeout; } } diff --git a/src/Periodic/PdoPeriodicPing.php b/src/Periodic/PdoPeriodicPing.php index 0656430..22b0ebf0 100644 --- a/src/Periodic/PdoPeriodicPing.php +++ b/src/Periodic/PdoPeriodicPing.php @@ -59,15 +59,11 @@ public function getInterval(): int */ public function getTimeout(): int { - trigger_deprecation('gos/web-socket-bundle', '3.9', '%s() is deprecated and will be removed in 4.0, call %s::getInterval() instead.', __METHOD__, self::class); - return $this->getInterval(); } public function setTimeout(int $timeout): void { - trigger_deprecation('gos/web-socket-bundle', '3.9', '%s() is deprecated and will be removed in 4.0, set the timeout through the constructor instead.', __METHOD__); - $this->interval = $timeout; } } diff --git a/src/Periodic/PeriodicMemoryUsage.php b/src/Periodic/PeriodicMemoryUsage.php index a1a17f1..22f4ad4 100644 --- a/src/Periodic/PeriodicMemoryUsage.php +++ b/src/Periodic/PeriodicMemoryUsage.php @@ -26,8 +26,6 @@ public function getInterval(): int */ public function getTimeout(): int { - trigger_deprecation('gos/web-socket-bundle', '3.9', '%s() is deprecated and will be removed in 4.0, call %s::getInterval() instead.', __METHOD__, self::class); - return $this->getInterval(); } } diff --git a/src/Pusher/AbstractPusher.php b/src/Pusher/AbstractPusher.php index 06e3796..0e97a7d 100644 --- a/src/Pusher/AbstractPusher.php +++ b/src/Pusher/AbstractPusher.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Router\WampRouter; use Symfony\Component\Serializer\SerializerInterface; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', AbstractPusher::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ abstract class AbstractPusher implements PusherInterface { diff --git a/src/Pusher/AbstractServerPushHandler.php b/src/Pusher/AbstractServerPushHandler.php index 7890375..fd22683 100644 --- a/src/Pusher/AbstractServerPushHandler.php +++ b/src/Pusher/AbstractServerPushHandler.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Pusher; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', AbstractServerPushHandler::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ abstract class AbstractServerPushHandler implements ServerPushHandlerInterface { diff --git a/src/Pusher/Amqp/AmqpConnectionFactory.php b/src/Pusher/Amqp/AmqpConnectionFactory.php index 58b1a7e..2122c48 100644 --- a/src/Pusher/Amqp/AmqpConnectionFactory.php +++ b/src/Pusher/Amqp/AmqpConnectionFactory.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Pusher\Exception\PusherUnsupportedException; use Symfony\Component\OptionsResolver\OptionsResolver; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', AmqpConnectionFactory::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class AmqpConnectionFactory implements AmqpConnectionFactoryInterface { diff --git a/src/Pusher/Amqp/AmqpConnectionFactoryInterface.php b/src/Pusher/Amqp/AmqpConnectionFactoryInterface.php index 325984a..ccd1bc3 100644 --- a/src/Pusher/Amqp/AmqpConnectionFactoryInterface.php +++ b/src/Pusher/Amqp/AmqpConnectionFactoryInterface.php @@ -4,10 +4,8 @@ use Gos\Bundle\WebSocketBundle\Pusher\Exception\PusherUnsupportedException; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" interface is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', AmqpConnectionFactoryInterface::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ interface AmqpConnectionFactoryInterface { diff --git a/src/Pusher/Amqp/AmqpPusher.php b/src/Pusher/Amqp/AmqpPusher.php index f23eff6..b28c8fe 100644 --- a/src/Pusher/Amqp/AmqpPusher.php +++ b/src/Pusher/Amqp/AmqpPusher.php @@ -8,10 +8,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\SerializerInterface; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', AmqpPusher::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class AmqpPusher extends AbstractPusher { diff --git a/src/Pusher/Amqp/AmqpServerPushHandler.php b/src/Pusher/Amqp/AmqpServerPushHandler.php index ef89d80..e8588a4 100644 --- a/src/Pusher/Amqp/AmqpServerPushHandler.php +++ b/src/Pusher/Amqp/AmqpServerPushHandler.php @@ -17,10 +17,8 @@ use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', AmqpServerPushHandler::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class AmqpServerPushHandler extends AbstractServerPushHandler implements LoggerAwareInterface { diff --git a/src/Pusher/DataCollectingPusherDecorator.php b/src/Pusher/DataCollectingPusherDecorator.php index a1f624e..3ae3939 100644 --- a/src/Pusher/DataCollectingPusherDecorator.php +++ b/src/Pusher/DataCollectingPusherDecorator.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\DataCollector\WebsocketDataCollector; use Symfony\Component\Stopwatch\Stopwatch; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', DataCollectingPusherDecorator::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class DataCollectingPusherDecorator implements PusherInterface { diff --git a/src/Pusher/Exception/PusherUnsupportedException.php b/src/Pusher/Exception/PusherUnsupportedException.php index a73c6fa..b7fbe0e 100644 --- a/src/Pusher/Exception/PusherUnsupportedException.php +++ b/src/Pusher/Exception/PusherUnsupportedException.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Pusher\Exception; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0.', PusherUnsupportedException::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class PusherUnsupportedException extends \RuntimeException { diff --git a/src/Pusher/Message.php b/src/Pusher/Message.php index 73b201c..062045b 100644 --- a/src/Pusher/Message.php +++ b/src/Pusher/Message.php @@ -2,12 +2,10 @@ namespace Gos\Bundle\WebSocketBundle\Pusher; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', Message::class); - /** * @internal * - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class Message { diff --git a/src/Pusher/PusherInterface.php b/src/Pusher/PusherInterface.php index a02bd5c..320902a 100644 --- a/src/Pusher/PusherInterface.php +++ b/src/Pusher/PusherInterface.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Pusher; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" interface is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PusherInterface::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ interface PusherInterface { diff --git a/src/Pusher/PusherRegistry.php b/src/Pusher/PusherRegistry.php index a915acf..97c952d 100644 --- a/src/Pusher/PusherRegistry.php +++ b/src/Pusher/PusherRegistry.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Pusher; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PusherRegistry::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class PusherRegistry { diff --git a/src/Pusher/ServerPushHandlerInterface.php b/src/Pusher/ServerPushHandlerInterface.php index ed615f4..b4c6e75 100644 --- a/src/Pusher/ServerPushHandlerInterface.php +++ b/src/Pusher/ServerPushHandlerInterface.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Server\App\PushableWampServerInterface; use React\EventLoop\LoopInterface; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" interface is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', ServerPushHandlerInterface::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ interface ServerPushHandlerInterface { diff --git a/src/Pusher/ServerPushHandlerRegistry.php b/src/Pusher/ServerPushHandlerRegistry.php index a952e4a..3867e8c 100644 --- a/src/Pusher/ServerPushHandlerRegistry.php +++ b/src/Pusher/ServerPushHandlerRegistry.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Pusher; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', ServerPushHandlerRegistry::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class ServerPushHandlerRegistry { diff --git a/src/Pusher/Wamp/WampConnectionFactory.php b/src/Pusher/Wamp/WampConnectionFactory.php index d1cfb1b..6a2426c 100644 --- a/src/Pusher/Wamp/WampConnectionFactory.php +++ b/src/Pusher/Wamp/WampConnectionFactory.php @@ -9,10 +9,8 @@ use Psr\Log\LoggerAwareTrait; use Symfony\Component\OptionsResolver\OptionsResolver; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use "%s" instead.', WampConnectionFactory::class, ClientFactory::class); - /** - * @deprecated to be removed in 4.0, use Gos\Component\WebSocketClient\Wamp\ClientFactory from the gos/websocket-client package instead + * deprecated to be removed in 4.0, use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory from the gos/websocket-client package instead */ final class WampConnectionFactory implements WampConnectionFactoryInterface, LoggerAwareInterface { diff --git a/src/Pusher/Wamp/WampConnectionFactoryInterface.php b/src/Pusher/Wamp/WampConnectionFactoryInterface.php index 3230ec8..aa4f25b 100644 --- a/src/Pusher/Wamp/WampConnectionFactoryInterface.php +++ b/src/Pusher/Wamp/WampConnectionFactoryInterface.php @@ -5,7 +5,7 @@ use Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface; /** - * @deprecated to be removed in 4.0, use the Gos\Component\WebSocketClient\Wamp\ClientFactoryInterface from the gos/websocket-client package instead + * deprecated to be removed in 4.0, use the Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface from the gos/websocket-client package instead */ interface WampConnectionFactoryInterface extends ClientFactoryInterface { diff --git a/src/Pusher/Wamp/WampPusher.php b/src/Pusher/Wamp/WampPusher.php index 31057e2..089303f 100644 --- a/src/Pusher/Wamp/WampPusher.php +++ b/src/Pusher/Wamp/WampPusher.php @@ -8,10 +8,8 @@ use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; use Symfony\Component\Serializer\SerializerInterface; -trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" class is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', WampPusher::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ final class WampPusher extends AbstractPusher { diff --git a/src/Server/App/Dispatcher/TopicDispatcher.php b/src/Server/App/Dispatcher/TopicDispatcher.php index 0c85542..8b6cc01 100644 --- a/src/Server/App/Dispatcher/TopicDispatcher.php +++ b/src/Server/App/Dispatcher/TopicDispatcher.php @@ -47,8 +47,6 @@ public function __construct( $this->topicRegistry = $topicRegistry; if ($router instanceof WampRouter) { - trigger_deprecation('gos/web-socket-bundle', '3.13', 'Passing a "%s" instance as the second argument of the "%s" class constructor is deprecated and will not be supported in 4.0.', WampRouter::class, self::class); - if (!$topicPeriodicTimer instanceof TopicPeriodicTimer) { throw new \InvalidArgumentException(sprintf('Argument 3 of the %s constructor must be an instance of %s, "%s" given.', self::class, TopicPeriodicTimer::class, get_debug_type($topicPeriodicTimer))); } @@ -85,8 +83,6 @@ public function onSubscribe(ConnectionInterface $conn, Topic $topic, WampRequest */ public function onPush(WampRequest $request, $data, string $provider): void { - trigger_deprecation('gos/web-socket-bundle', '3.7', '%s() is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', __METHOD__); - $topic = $this->topicManager->getTopic($request->getMatched()); $this->dispatch(self::PUSH, null, $topic, $request, $data, null, null, $provider); } diff --git a/src/Server/App/PushableWampServerInterface.php b/src/Server/App/PushableWampServerInterface.php index 4b22949..f71e95d 100644 --- a/src/Server/App/PushableWampServerInterface.php +++ b/src/Server/App/PushableWampServerInterface.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Router\WampRequest; use Ratchet\Wamp\WampServerInterface; -trigger_deprecation('gos/web-socket-bundle', '3.7', 'The "%s" interface is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PushableWampServerInterface::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ interface PushableWampServerInterface extends WampServerInterface { diff --git a/src/Server/App/WampApplication.php b/src/Server/App/WampApplication.php index 95fc9da..5b99280 100644 --- a/src/Server/App/WampApplication.php +++ b/src/Server/App/WampApplication.php @@ -100,8 +100,6 @@ public function onPublish(ConnectionInterface $conn, $topic, $event, array $excl */ public function onPush(WampRequest $request, $data, $provider): void { - trigger_deprecation('gos/web-socket-bundle', '3.7', '%s() is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', __METHOD__); - if (null !== $this->logger) { $this->logger->info( sprintf('Pusher %s has pushed', $provider), diff --git a/src/Server/EntryPoint.php b/src/Server/EntryPoint.php index 63a5c28..97d7ec3 100644 --- a/src/Server/EntryPoint.php +++ b/src/Server/EntryPoint.php @@ -2,10 +2,8 @@ namespace Gos\Bundle\WebSocketBundle\Server; -trigger_deprecation('gos/web-socket-bundle', '3.7', 'The "%s" class is deprecated and will be removed in 4.0, use the "%s" class instead.', EntryPoint::class, ServerLauncher::class); - /** - * @deprecated to be removed in 4.0, use the `Gos\Bundle\WebSocketBundle\Server\ServerLauncher` class instead + * deprecated to be removed in 4.0, use the `Gos\Bundle\WebSocketBundle\Server\ServerLauncher` class instead */ final class EntryPoint extends ServerLauncher { diff --git a/src/Topic/PushableTopicInterface.php b/src/Topic/PushableTopicInterface.php index 46097bd..56a57d0 100644 --- a/src/Topic/PushableTopicInterface.php +++ b/src/Topic/PushableTopicInterface.php @@ -5,10 +5,8 @@ use Gos\Bundle\WebSocketBundle\Router\WampRequest; use Ratchet\Wamp\Topic; -trigger_deprecation('gos/web-socket-bundle', '3.7', 'The "%s" interface is deprecated and will be removed in 4.0, use the symfony/messenger component instead.', PushableTopicInterface::class); - /** - * @deprecated to be removed in 4.0, use the symfony/messenger component instead + * deprecated to be removed in 4.0, use the symfony/messenger component instead */ interface PushableTopicInterface { diff --git a/src/Topic/TopicManager.php b/src/Topic/TopicManager.php index 32eacee..7aaa66d 100644 --- a/src/Topic/TopicManager.php +++ b/src/Topic/TopicManager.php @@ -24,8 +24,6 @@ class TopicManager implements WsServerInterface, WampServerInterface */ public function setWampApplication(WampServerInterface $app): void { - trigger_deprecation('gos/web-socket-bundle', '3.7', '%s() is deprecated and will be removed in 4.0, the dependency will be injected through the constructor instead.', __METHOD__); - $this->app = $app; } From 6757af6b207b25cd67139e266445acf87e57287f Mon Sep 17 00:00:00 2001 From: OleksandrProtsiuk Date: Mon, 6 Oct 2025 13:12:15 +0300 Subject: [PATCH 4/6] Revert "WebSocket client from gos/websocket-client was added" This reverts commit 3e100f85ad93dd0e92ac777ecaa65e22facf1441. --- .../GosWebSocketExtension.php | 8 +- src/Exception/BadResponseException.php | 24 - src/Exception/WebSocketException.php | 24 - src/Pusher/Wamp/WampConnectionFactory.php | 6 +- .../Wamp/WampConnectionFactoryInterface.php | 4 +- src/Pusher/Wamp/WampPusher.php | 2 +- src/Wamp/Client.php | 428 ------------------ src/Wamp/ClientFactory.php | 81 ---- src/Wamp/ClientFactoryInterface.php | 25 - src/Wamp/ClientInterface.php | 72 --- src/Wamp/PayloadGenerator.php | 74 --- src/Wamp/PayloadGeneratorInterface.php | 27 -- src/Wamp/Protocol.php | 37 -- src/Wamp/WebsocketPayload.php | 198 -------- .../GosWebSocketExtensionTest.php | 4 +- .../Pusher/Wamp/WampConnectionFactoryTest.php | 2 +- tests/Pusher/Wamp/WampPusherTest.php | 2 +- 17 files changed, 15 insertions(+), 1003 deletions(-) delete mode 100644 src/Exception/BadResponseException.php delete mode 100644 src/Exception/WebSocketException.php delete mode 100644 src/Wamp/Client.php delete mode 100644 src/Wamp/ClientFactory.php delete mode 100644 src/Wamp/ClientFactoryInterface.php delete mode 100644 src/Wamp/ClientInterface.php delete mode 100644 src/Wamp/PayloadGenerator.php delete mode 100644 src/Wamp/PayloadGeneratorInterface.php delete mode 100644 src/Wamp/Protocol.php delete mode 100644 src/Wamp/WebsocketPayload.php diff --git a/src/DependencyInjection/GosWebSocketExtension.php b/src/DependencyInjection/GosWebSocketExtension.php index 0cb2b1a..b8fc505 100644 --- a/src/DependencyInjection/GosWebSocketExtension.php +++ b/src/DependencyInjection/GosWebSocketExtension.php @@ -16,10 +16,10 @@ use Gos\Bundle\WebSocketBundle\RPC\RpcInterface; use Gos\Bundle\WebSocketBundle\Server\Type\ServerInterface; use Gos\Bundle\WebSocketBundle\Topic\TopicInterface; -use Gos\Bundle\WebSocketBundle\Wamp\Client; -use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory; -use Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface; -use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; +use Gos\Component\WebSocketClient\Wamp\Client; +use Gos\Component\WebSocketClient\Wamp\ClientFactory; +use Gos\Component\WebSocketClient\Wamp\ClientFactoryInterface; +use Gos\Component\WebSocketClient\Wamp\ClientInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; diff --git a/src/Exception/BadResponseException.php b/src/Exception/BadResponseException.php deleted file mode 100644 index 40650e3..0000000 --- a/src/Exception/BadResponseException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -class BadResponseException extends WebSocketException -{ -} diff --git a/src/Exception/WebSocketException.php b/src/Exception/WebSocketException.php deleted file mode 100644 index 5a9d9cb..0000000 --- a/src/Exception/WebSocketException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -class WebSocketException extends \Exception -{ -} diff --git a/src/Pusher/Wamp/WampConnectionFactory.php b/src/Pusher/Wamp/WampConnectionFactory.php index 6a2426c..d646f6c 100644 --- a/src/Pusher/Wamp/WampConnectionFactory.php +++ b/src/Pusher/Wamp/WampConnectionFactory.php @@ -2,9 +2,9 @@ namespace Gos\Bundle\WebSocketBundle\Pusher\Wamp; -use Gos\Bundle\WebSocketBundle\Wamp\Client; -use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory; -use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; +use Gos\Component\WebSocketClient\Wamp\Client; +use Gos\Component\WebSocketClient\Wamp\ClientFactory; +use Gos\Component\WebSocketClient\Wamp\ClientInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Component\OptionsResolver\OptionsResolver; diff --git a/src/Pusher/Wamp/WampConnectionFactoryInterface.php b/src/Pusher/Wamp/WampConnectionFactoryInterface.php index aa4f25b..23ce6a9 100644 --- a/src/Pusher/Wamp/WampConnectionFactoryInterface.php +++ b/src/Pusher/Wamp/WampConnectionFactoryInterface.php @@ -2,7 +2,9 @@ namespace Gos\Bundle\WebSocketBundle\Pusher\Wamp; -use Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface; +use Gos\Component\WebSocketClient\Wamp\ClientFactoryInterface; + +trigger_deprecation('gos/web-socket-bundle', '3.1', 'The "%s" interface is deprecated and will be removed in 4.0, use "%s" instead.', WampConnectionFactoryInterface::class, ClientFactoryInterface::class); /** * deprecated to be removed in 4.0, use the Gos\Bundle\WebSocketBundle\Wamp\ClientFactoryInterface from the gos/websocket-client package instead diff --git a/src/Pusher/Wamp/WampPusher.php b/src/Pusher/Wamp/WampPusher.php index 089303f..76cc194 100644 --- a/src/Pusher/Wamp/WampPusher.php +++ b/src/Pusher/Wamp/WampPusher.php @@ -5,7 +5,7 @@ use Gos\Bundle\WebSocketBundle\Pusher\AbstractPusher; use Gos\Bundle\WebSocketBundle\Pusher\Message; use Gos\Bundle\WebSocketBundle\Router\WampRouter; -use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; +use Gos\Component\WebSocketClient\Wamp\ClientInterface; use Symfony\Component\Serializer\SerializerInterface; /** diff --git a/src/Wamp/Client.php b/src/Wamp/Client.php deleted file mode 100644 index ab9defc..0000000 --- a/src/Wamp/Client.php +++ /dev/null @@ -1,428 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -final class Client implements ClientInterface, LoggerAwareInterface -{ - use LoggerAwareTrait; - - /** - * @var bool - */ - private $connected = false; - - /** - * @var string - */ - private $endpoint; - - /** - * @var string|null - */ - private $target; - - /** - * @var resource|null - */ - private $socket; - - /** - * @var string|null - */ - private $sessionId; - - /** - * @var string - */ - private $serverHost; - - /** - * @var int - */ - private $serverPort; - - /** - * @var bool - */ - private $secured = false; - - /** - * @var string|null - */ - private $origin; - - /** - * @var PayloadGeneratorInterface - */ - private $payloadGenerator; - - public function __construct(string $host, int $port, bool $secured = false, ?string $origin = null, ?PayloadGeneratorInterface $payloadGenerator = null) - { - $this->serverHost = $host; - $this->serverPort = $port; - $this->secured = $secured; - $this->origin = null !== $origin ? $origin : $host; - $this->payloadGenerator = $payloadGenerator ?: new PayloadGenerator(); - - $this->endpoint = sprintf( - '%s://%s:%s', - $secured ? 'ssl' : 'tcp', - $host, - $port - ); - } - - /** - * @return string The session identifier for the connection - * - * @throws BadResponseException if a response could not be received from the websocket server - * @throws WebsocketException if the target URI is invalid - */ - public function connect(string $target = '/'): string - { - if ($this->connected) { - return $this->sessionId; - } - - $socket = @stream_socket_client($this->endpoint, $errno, $errstr); - - if (false === $socket) { - if (null !== $this->logger) { - $this->logger->error('Could not open socket.', ['errno' => $errno, 'errstr' => $errstr]); - } - - throw new BadResponseException('Could not open socket. Reason: '.$errstr, $errno); - } - - $this->target = $target; - $this->socket = $socket; - - $this->verifyResponse($this->upgradeProtocol($this->target)); - - $payload = json_decode($this->read()); - - if (false === $payload) { - throw new BadResponseException('WAMP Server sent an invalid payload.'); - } - - if (Protocol::MSG_WELCOME !== $payload[0]) { - if (null !== $this->logger) { - $this->logger->error('WAMP Server did not send a welcome message.', ['payload' => $payload]); - } - - throw new BadResponseException('WAMP Server did not send a welcome message.'); - } - - $this->connected = true; - - return $this->sessionId = $payload[1]; - } - - /** - * @return string|false Response body from the request or boolean false on failure - * - * @throws WebsocketException if the target URI is invalid - */ - private function upgradeProtocol(string $target) - { - $key = $this->generateKey(); - - if (false === strpos($target, '/')) { - if (null !== $this->logger) { - $this->logger->error('Invalid target path for WAMP server.', ['target' => $target]); - } - - throw new WebsocketException('WAMP server target must contain a "/"'); - } - - $protocol = $this->secured ? 'wss' : 'ws'; - - $out = "GET {$protocol}://{$this->serverHost}:{$this->serverPort}{$target} HTTP/1.1\r\n"; - $out .= "Host: {$this->serverHost}:{$this->serverPort}\r\n"; - $out .= "Pragma: no-cache\r\n"; - $out .= "Cache-Control: no-cache\r\n"; - $out .= "Upgrade: WebSocket\r\n"; - $out .= "Connection: Upgrade\r\n"; - $out .= "Sec-WebSocket-Key: $key\r\n"; - $out .= "Sec-WebSocket-Protocol: wamp\r\n"; - $out .= "Sec-WebSocket-Version: 13\r\n"; - $out .= "Origin: {$this->origin}\r\n\r\n"; - - fwrite($this->socket, $out); - - return fgets($this->socket); - } - - /** - * @param string|false $response Response body from the upgrade request or boolean false on failure - * - * @throws BadResponseException if an invalid response was received - */ - private function verifyResponse($response): void - { - if (false === $response) { - if (null !== $this->logger) { - $this->logger->error('WAMP Server did not respond properly'); - } - - throw new BadResponseException('WAMP Server did not respond properly'); - } - - $responseStatus = substr($response, 0, 12); - - if ('HTTP/1.1 101' !== $responseStatus) { - if (null !== $this->logger) { - $this->logger->error('Unexpected HTTP response from WAMP server.', ['response' => $response]); - } - - throw new BadResponseException(sprintf('Unexpected response status. Expected "HTTP/1.1 101", got "%s".', $responseStatus)); - } - } - - /** - * Read the buffer and return the oldest event in stack. - * - * @see https://tools.ietf.org/html/rfc6455#section-5.2 - * - * @throws BadResponseException if the buffer could not be read - */ - private function read(): string - { - $streamBody = stream_get_contents($this->socket, stream_get_meta_data($this->socket)['unread_bytes']); - - if (false === $streamBody) { - if (null !== $this->logger) { - $this->logger->error('The stream buffer could not be read.', ['error' => error_get_last()]); - } - - throw new BadResponseException('The stream buffer could not be read.'); - } - - $startPos = strpos($streamBody, '['); - $endPos = strpos($streamBody, ']'); - - if (false === $startPos || false === $endPos) { - if (null !== $this->logger) { - $this->logger->error('Could not extract response body from stream.', ['body' => $streamBody]); - } - - throw new BadResponseException('Could not extract response body from stream.'); - } - - return substr( - $streamBody, - $startPos, - $endPos - ); - } - - /** - * @throws WebsocketException if the connection could not be disconnected cleanly - */ - public function disconnect(): bool - { - if (false === $this->connected) { - return true; - } - - if (null === $this->socket) { - return true; - } - - $this->send($this->payloadGenerator->generateClosePayload(), WebsocketPayload::OPCODE_CLOSE); - - $firstByte = fread($this->socket, 1); - - if (false === $firstByte) { - if (null !== $this->logger) { - $this->logger->error('Could not extract the payload from the buffer.', ['error' => error_get_last()]); - } - - throw new WebsocketException('Could not extract the payload from the buffer.'); - } - - /** @phpstan-var int<0, 255> $payloadLength */ - $payloadLength = \ord($firstByte); - $payload = fread($this->socket, $payloadLength); - - if (false === $payload) { - if (null !== $this->logger) { - $this->logger->error('Could not extract the payload from the buffer.', ['error' => error_get_last()]); - } - - throw new WebsocketException('Could not extract the payload from the buffer.'); - } - - if ($payloadLength >= 2) { - $bin = $payload[0].$payload[1]; - $status = bindec(sprintf('%08b%08b', \ord($payload[0]), \ord($payload[1]))); - - $this->send($bin.'Close acknowledged: '.$status, WebsocketPayload::OPCODE_CLOSE); - } - - fclose($this->socket); - $this->connected = false; - - return true; - } - - /** - * @param mixed $data Any JSON encodable data - * - * @throws WebsocketException if the data cannot be encoded properly - */ - private function send($data, int $opcode = WebsocketPayload::OPCODE_TEXT): void - { - if (\is_array($data)) { - $payload = json_encode($data); - - if (false === $payload) { - throw new WebsocketException('The data could not be encoded: '.json_last_error_msg()); - } - } elseif (is_scalar($data)) { - $payload = $data; - } else { - throw new WebsocketException('The data must be an array or a scalar value.'); - } - - $encoded = $this->payloadGenerator->encode( - (new WebsocketPayload()) - ->setOpcode($opcode) - ->setMask(0x1) - ->setPayload($payload) - ); - - // Check if the connection was reset, if so try to reconnect - if (false === @fwrite($this->socket, $encoded)) { - $this->connected = false; - $this->connect($this->target); - - fwrite($this->socket, $encoded); - } - } - - /** - * Establish a prefix on server. - * - * @see http://wamp.ws/spec#prefix_message - */ - public function prefix(string $prefix, string $uri): void - { - if (null !== $this->logger) { - $this->logger->info(sprintf('Establishing prefix "%s" for URI "%s"', $prefix, $uri)); - } - - $this->send([Protocol::MSG_PREFIX, $prefix, $uri]); - } - - /** - * Call a procedure on server. - * - * @see http://wamp.ws/spec#call_message - * - * @param array|mixed $args Arguments for the message either as an array or variadic set of parameters - */ - public function call(string $procUri, $args): void - { - if (!\is_array($args)) { - $args = \func_get_args(); - array_shift($args); - } - - if (null !== $this->logger) { - $this->logger->info( - sprintf('Websocket client calling %s', $procUri), - [ - 'callArguments' => $args, - ] - ); - } - - $this->send( - array_merge( - [Protocol::MSG_CALL, uniqid('', true), $procUri], - $args - ) - ); - } - - /** - * The client will send an event to all clients connected to the server who have subscribed to the topicURI. - * - * @see http://wamp.ws/spec#publish_message - * - * @param string[] $exclude - * @param string[] $eligible - */ - public function publish(string $topicUri, string $payload, array $exclude = [], array $eligible = []): void - { - if (null !== $this->logger) { - $this->logger->info( - sprintf('Websocket client publishing to %s', $topicUri), - [ - 'payload' => $payload, - 'excludedIds' => $exclude, - 'eligibleIds' => $eligible, - ] - ); - } - - $this->send([Protocol::MSG_PUBLISH, $topicUri, $payload, $exclude, $eligible]); - } - - /** - * Subscribers receive PubSub events published by subscribers via the EVENT message. The EVENT message contains the topicURI, the topic under which the event was published, and event, the PubSub event payload. - */ - public function event(string $topicUri, string $payload): void - { - if (null !== $this->logger) { - $this->logger->info( - sprintf('Websocket client sending event to %s', $topicUri), - [ - 'payload' => $payload, - ] - ); - } - - $this->send([Protocol::MSG_EVENT, $topicUri, $payload]); - } - - private function generateKey(int $length = 16): string - { - $c = 0; - $tmp = ''; - - while ($c++ * 16 < $length) { - $tmp .= md5((string) mt_rand(), true); - } - - return base64_encode(substr($tmp, 0, $length)); - } - - public function isConnected(): bool - { - return $this->connected; - } -} diff --git a/src/Wamp/ClientFactory.php b/src/Wamp/ClientFactory.php deleted file mode 100644 index f0f1b24..0000000 --- a/src/Wamp/ClientFactory.php +++ /dev/null @@ -1,81 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -final class ClientFactory implements ClientFactoryInterface, LoggerAwareInterface -{ - use LoggerAwareTrait; - - /** - * @var array - */ - private $config; - - public function __construct(array $config) - { - $this->config = $this->resolveConfig($config); - } - - public function createConnection(): ClientInterface - { - $client = new Client( - $this->config['host'], - $this->config['port'], - $this->config['ssl'], - $this->config['origin'] - ); - - if (null !== $this->logger) { - $client->setLogger($this->logger); - } - - return $client; - } - - private function resolveConfig(array $config): array - { - $resolver = new OptionsResolver(); - - $resolver->setRequired( - [ - 'host', - 'port', - ] - ); - - $resolver->setDefaults( - [ - 'ssl' => false, - 'origin' => null, - ] - ); - - $resolver->setAllowedTypes('host', 'string'); - $resolver->setAllowedTypes('port', ['string', 'integer']); - $resolver->setAllowedTypes('ssl', 'boolean'); - $resolver->setAllowedTypes('origin', ['string', 'null']); - - return $resolver->resolve($config); - } -} diff --git a/src/Wamp/ClientFactoryInterface.php b/src/Wamp/ClientFactoryInterface.php deleted file mode 100644 index 27c30f2..0000000 --- a/src/Wamp/ClientFactoryInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -interface ClientFactoryInterface -{ - public function createConnection(): ClientInterface; -} diff --git a/src/Wamp/ClientInterface.php b/src/Wamp/ClientInterface.php deleted file mode 100644 index ae6ae24..0000000 --- a/src/Wamp/ClientInterface.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -interface ClientInterface -{ - /** - * @return string The session identifier for the connection - * - * @throws BadResponseException if a response could not be received from the websocket server - * @throws WebsocketException if the target URI is invalid - */ - public function connect(string $target = '/'): string; - - /** - * @throws WebsocketException if the connection could not be disconnected cleanly - */ - public function disconnect(): bool; - - public function isConnected(): bool; - - /** - * Establish a prefix on server. - * - * @see http://wamp.ws/spec#prefix_message - */ - public function prefix(string $prefix, string $uri): void; - - /** - * Call a procedure on server. - * - * @see http://wamp.ws/spec#call_message - * - * @param array|mixed $args Arguments for the message either as an array or variadic set of parameters - */ - public function call(string $procUri, $args): void; - - /** - * The client will send an event to all clients connected to the server who have subscribed to the topicURI. - * - * @see http://wamp.ws/spec#publish_message - * - * @param string[] $exclude - * @param string[] $eligible - */ - public function publish(string $topicUri, string $payload, array $exclude = [], array $eligible = []): void; - - /** - * Subscribers receive PubSub events published by subscribers via the EVENT message. The EVENT message contains the topicURI, the topic under which the event was published, and event, the PubSub event payload. - */ - public function event(string $topicUri, string $payload): void; -} diff --git a/src/Wamp/PayloadGenerator.php b/src/Wamp/PayloadGenerator.php deleted file mode 100644 index 8df4984..0000000 --- a/src/Wamp/PayloadGenerator.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -final class PayloadGenerator implements PayloadGeneratorInterface -{ - public function encode(WebsocketPayload $websocketPayload): string - { - $payload = $websocketPayload->getFin() << 1 | $websocketPayload->getRsv1(); - $payload = $payload << 1 | $websocketPayload->getRsv2(); - $payload = $payload << 1 | $websocketPayload->getRsv3(); - $payload = $payload << 4 | $websocketPayload->getOpcode(); - $payload = $payload << 1 | $websocketPayload->getMask(); - - if ($websocketPayload->getLength() <= 125) { - $payload = $payload << 7 | $websocketPayload->getLength(); - $payload = pack('n', $payload); - } elseif ($websocketPayload->getLength() <= 0xffff) { - $payload = $payload << 7 | 126; - $payload = pack('n', $payload).pack('n*', $websocketPayload->getLength()); - } else { - $payload = $payload << 7 | 127; - $payload = pack('n', $payload).pack('NN', ($websocketPayload->getLength() & 0xffffffff00000000) >> 32, $websocketPayload->getLength() & 0x00000000ffffffff); - } - - if (0x1 == $websocketPayload->getMask()) { - $payload .= $websocketPayload->getMaskKey(); - $data = $this->maskData($websocketPayload->getPayload(), $websocketPayload->getMaskKey()); - } else { - $data = $websocketPayload->getPayload(); - } - - return $payload.$data; - } - - public function generateClosePayload(): string - { - $str = ''; - - foreach (str_split(sprintf('%016b', 1000), 8) as $binstr) { - $str .= \chr((int) bindec($binstr)); - } - - return $str.'ttfn'; - } - - private function maskData(?string $data, ?string $key): string - { - $masked = ''; - - for ($i = 0; $i < \strlen($data); ++$i) { - $masked .= $data[$i] ^ $key[$i % 4]; - } - - return $masked; - } -} diff --git a/src/Wamp/PayloadGeneratorInterface.php b/src/Wamp/PayloadGeneratorInterface.php deleted file mode 100644 index 292dca9..0000000 --- a/src/Wamp/PayloadGeneratorInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -interface PayloadGeneratorInterface -{ - public function encode(WebsocketPayload $websocketPayload): string; - - public function generateClosePayload(): string; -} diff --git a/src/Wamp/Protocol.php b/src/Wamp/Protocol.php deleted file mode 100644 index 8fd8098..0000000 --- a/src/Wamp/Protocol.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -final class Protocol -{ - public const MSG_WELCOME = 0; - public const MSG_PREFIX = 1; - public const MSG_CALL = 2; - public const MSG_CALL_RESULT = 3; - public const MSG_CALL_ERROR = 4; - public const MSG_SUBSCRIBE = 5; - public const MSG_UNSUBSCRIBE = 6; - public const MSG_PUBLISH = 7; - public const MSG_EVENT = 8; - - private function __construct() - { - } -} diff --git a/src/Wamp/WebsocketPayload.php b/src/Wamp/WebsocketPayload.php deleted file mode 100644 index 6e8f383..0000000 --- a/src/Wamp/WebsocketPayload.php +++ /dev/null @@ -1,198 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - */ -final class WebsocketPayload -{ - public const OPCODE_CONTINUE = 0x0; - public const OPCODE_TEXT = 0x1; - public const OPCODE_BINARY = 0x2; - public const OPCODE_NON_CONTROL_RESERVED_1 = 0x3; - public const OPCODE_NON_CONTROL_RESERVED_2 = 0x4; - public const OPCODE_NON_CONTROL_RESERVED_3 = 0x5; - public const OPCODE_NON_CONTROL_RESERVED_4 = 0x6; - public const OPCODE_NON_CONTROL_RESERVED_5 = 0x7; - public const OPCODE_CLOSE = 0x8; - public const OPCODE_PING = 0x9; - public const OPCODE_PONG = 0xA; - public const OPCODE_CONTROL_RESERVED_1 = 0xB; - public const OPCODE_CONTROL_RESERVED_2 = 0xC; - public const OPCODE_CONTROL_RESERVED_3 = 0xD; - public const OPCODE_CONTROL_RESERVED_4 = 0xE; - public const OPCODE_CONTROL_RESERVED_5 = 0xF; - - /** - * @var int - */ - private $fin = 0x1; - - /** - * @var int - */ - private $rsv1 = 0x0; - - /** - * @var int - */ - private $rsv2 = 0x0; - - /** - * @var int - */ - private $rsv3 = 0x0; - - /** - * @var int|null - */ - private $opcode; - - /** - * @var int - */ - private $mask = 0x0; - - /** - * @var string - */ - private $maskKey; - - /** - * @var mixed - */ - private $payload; - - public function setFin(int $fin): self - { - $this->fin = $fin; - - return $this; - } - - public function getFin(): int - { - return $this->fin; - } - - public function setRsv1(int $rsv1): self - { - $this->rsv1 = $rsv1; - - return $this; - } - - public function getRsv1(): int - { - return $this->rsv1; - } - - public function setRsv2(int $rsv2): self - { - $this->rsv2 = $rsv2; - - return $this; - } - - public function getRsv2(): int - { - return $this->rsv2; - } - - public function setRsv3(int $rsv3): self - { - $this->rsv3 = $rsv3; - - return $this; - } - - public function getRsv3(): int - { - return $this->rsv3; - } - - public function setOpcode(?int $opcode): self - { - $this->opcode = $opcode; - - return $this; - } - - public function getOpcode(): ?int - { - return $this->opcode; - } - - public function setMask(int $mask): self - { - $this->mask = $mask; - - if (true == $this->mask) { - $this->generateMaskKey(); - } - - return $this; - } - - public function getMask(): int - { - return $this->mask; - } - - public function getLength(): int - { - return \strlen($this->getPayload()); - } - - public function setMaskKey(string $maskKey): self - { - $this->maskKey = $maskKey; - - return $this; - } - - public function getMaskKey(): ?string - { - return $this->maskKey; - } - - /** - * @param mixed $payload - */ - public function setPayload($payload): self - { - $this->payload = $payload; - - return $this; - } - - /** - * @return mixed - */ - public function getPayload() - { - return $this->payload; - } - - public function generateMaskKey(): string - { - $this->setMaskKey(random_bytes(4)); - - return $this->getMaskKey(); - } -} diff --git a/tests/DependencyInjection/GosWebSocketExtensionTest.php b/tests/DependencyInjection/GosWebSocketExtensionTest.php index 303b175..4b7b6bc 100644 --- a/tests/DependencyInjection/GosWebSocketExtensionTest.php +++ b/tests/DependencyInjection/GosWebSocketExtensionTest.php @@ -11,8 +11,8 @@ use Gos\Bundle\WebSocketBundle\GosWebSocketBundle; use Gos\Bundle\WebSocketBundle\Pusher\Amqp\AmqpConnectionFactory; use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampConnectionFactory; -use Gos\Bundle\WebSocketBundle\Wamp\Client; -use Gos\Bundle\WebSocketBundle\Wamp\ClientFactory; +use Gos\Component\WebSocketClient\Wamp\Client; +use Gos\Component\WebSocketClient\Wamp\ClientFactory; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Exception\RuntimeException; diff --git a/tests/Pusher/Wamp/WampConnectionFactoryTest.php b/tests/Pusher/Wamp/WampConnectionFactoryTest.php index 210cbb2..fa3aaea 100644 --- a/tests/Pusher/Wamp/WampConnectionFactoryTest.php +++ b/tests/Pusher/Wamp/WampConnectionFactoryTest.php @@ -3,7 +3,7 @@ namespace Gos\Bundle\WebSocketBundle\Tests\Pusher\Wamp; use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampConnectionFactory; -use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; +use Gos\Component\WebSocketClient\Wamp\ClientInterface; use PHPUnit\Framework\TestCase; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; diff --git a/tests/Pusher/Wamp/WampPusherTest.php b/tests/Pusher/Wamp/WampPusherTest.php index 9f9b723..0b6ac63 100644 --- a/tests/Pusher/Wamp/WampPusherTest.php +++ b/tests/Pusher/Wamp/WampPusherTest.php @@ -6,7 +6,7 @@ use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampConnectionFactoryInterface; use Gos\Bundle\WebSocketBundle\Pusher\Wamp\WampPusher; use Gos\Bundle\WebSocketBundle\Router\WampRouter; -use Gos\Bundle\WebSocketBundle\Wamp\ClientInterface; +use Gos\Component\WebSocketClient\Wamp\ClientInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\SerializerInterface; From 2ce69910fdc14438ab8b06ff5a624bb5909c0e27 Mon Sep 17 00:00:00 2001 From: OleksandrProtsiuk Date: Mon, 6 Oct 2025 13:34:58 +0300 Subject: [PATCH 5/6] - Component gos/websocket-client was added - Autoload configuration updated --- composer.json | 5 +- .../Exception/BadResponseException.php | 24 + .../Exception/WebSocketException.php | 24 + src/Component/WebSocketClient/Wamp/Client.php | 428 ++++++++++++++++++ .../WebSocketClient/Wamp/ClientFactory.php | 81 ++++ .../Wamp/ClientFactoryInterface.php | 25 + .../WebSocketClient/Wamp/ClientInterface.php | 72 +++ .../WebSocketClient/Wamp/PayloadGenerator.php | 74 +++ .../Wamp/PayloadGeneratorInterface.php | 27 ++ .../WebSocketClient/Wamp/Protocol.php | 37 ++ .../WebSocketClient/Wamp/WebsocketPayload.php | 198 ++++++++ 11 files changed, 994 insertions(+), 1 deletion(-) create mode 100644 src/Component/WebSocketClient/Exception/BadResponseException.php create mode 100644 src/Component/WebSocketClient/Exception/WebSocketException.php create mode 100644 src/Component/WebSocketClient/Wamp/Client.php create mode 100644 src/Component/WebSocketClient/Wamp/ClientFactory.php create mode 100644 src/Component/WebSocketClient/Wamp/ClientFactoryInterface.php create mode 100644 src/Component/WebSocketClient/Wamp/ClientInterface.php create mode 100644 src/Component/WebSocketClient/Wamp/PayloadGenerator.php create mode 100644 src/Component/WebSocketClient/Wamp/PayloadGeneratorInterface.php create mode 100644 src/Component/WebSocketClient/Wamp/Protocol.php create mode 100644 src/Component/WebSocketClient/Wamp/WebsocketPayload.php diff --git a/composer.json b/composer.json index 618d67e..80a83ca 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,10 @@ "twig/twig": "<1.36 || >=2.0,<2.6" }, "autoload": { - "psr-4": { "Gos\\Bundle\\WebSocketBundle\\": "src/" } + "psr-4": { + "Gos\\Bundle\\WebSocketBundle\\": "src/", + "Gos\\Component\\WebSocketClient\\": "src/Component/WebSocketClient/" + } }, "autoload-dev": { "psr-4": { "Gos\\Bundle\\WebSocketBundle\\Tests\\": "tests/" } diff --git a/src/Component/WebSocketClient/Exception/BadResponseException.php b/src/Component/WebSocketClient/Exception/BadResponseException.php new file mode 100644 index 0000000..cf10017 --- /dev/null +++ b/src/Component/WebSocketClient/Exception/BadResponseException.php @@ -0,0 +1,24 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +class BadResponseException extends WebSocketException +{ +} diff --git a/src/Component/WebSocketClient/Exception/WebSocketException.php b/src/Component/WebSocketClient/Exception/WebSocketException.php new file mode 100644 index 0000000..ccd103e --- /dev/null +++ b/src/Component/WebSocketClient/Exception/WebSocketException.php @@ -0,0 +1,24 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +class WebSocketException extends \Exception +{ +} diff --git a/src/Component/WebSocketClient/Wamp/Client.php b/src/Component/WebSocketClient/Wamp/Client.php new file mode 100644 index 0000000..df0d45a --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/Client.php @@ -0,0 +1,428 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class Client implements ClientInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var bool + */ + private $connected = false; + + /** + * @var string + */ + private $endpoint; + + /** + * @var string|null + */ + private $target; + + /** + * @var resource|null + */ + private $socket; + + /** + * @var string|null + */ + private $sessionId; + + /** + * @var string + */ + private $serverHost; + + /** + * @var int + */ + private $serverPort; + + /** + * @var bool + */ + private $secured = false; + + /** + * @var string|null + */ + private $origin; + + /** + * @var PayloadGeneratorInterface + */ + private $payloadGenerator; + + public function __construct(string $host, int $port, bool $secured = false, ?string $origin = null, ?PayloadGeneratorInterface $payloadGenerator = null) + { + $this->serverHost = $host; + $this->serverPort = $port; + $this->secured = $secured; + $this->origin = null !== $origin ? $origin : $host; + $this->payloadGenerator = $payloadGenerator ?: new PayloadGenerator(); + + $this->endpoint = sprintf( + '%s://%s:%s', + $secured ? 'ssl' : 'tcp', + $host, + $port + ); + } + + /** + * @return string The session identifier for the connection + * + * @throws BadResponseException if a response could not be received from the websocket server + * @throws WebsocketException if the target URI is invalid + */ + public function connect(string $target = '/'): string + { + if ($this->connected) { + return $this->sessionId; + } + + $socket = @stream_socket_client($this->endpoint, $errno, $errstr); + + if (false === $socket) { + if (null !== $this->logger) { + $this->logger->error('Could not open socket.', ['errno' => $errno, 'errstr' => $errstr]); + } + + throw new BadResponseException('Could not open socket. Reason: '.$errstr, $errno); + } + + $this->target = $target; + $this->socket = $socket; + + $this->verifyResponse($this->upgradeProtocol($this->target)); + + $payload = json_decode($this->read()); + + if (false === $payload) { + throw new BadResponseException('WAMP Server sent an invalid payload.'); + } + + if (Protocol::MSG_WELCOME !== $payload[0]) { + if (null !== $this->logger) { + $this->logger->error('WAMP Server did not send a welcome message.', ['payload' => $payload]); + } + + throw new BadResponseException('WAMP Server did not send a welcome message.'); + } + + $this->connected = true; + + return $this->sessionId = $payload[1]; + } + + /** + * @return string|false Response body from the request or boolean false on failure + * + * @throws WebsocketException if the target URI is invalid + */ + private function upgradeProtocol(string $target) + { + $key = $this->generateKey(); + + if (false === strpos($target, '/')) { + if (null !== $this->logger) { + $this->logger->error('Invalid target path for WAMP server.', ['target' => $target]); + } + + throw new WebsocketException('WAMP server target must contain a "/"'); + } + + $protocol = $this->secured ? 'wss' : 'ws'; + + $out = "GET {$protocol}://{$this->serverHost}:{$this->serverPort}{$target} HTTP/1.1\r\n"; + $out .= "Host: {$this->serverHost}:{$this->serverPort}\r\n"; + $out .= "Pragma: no-cache\r\n"; + $out .= "Cache-Control: no-cache\r\n"; + $out .= "Upgrade: WebSocket\r\n"; + $out .= "Connection: Upgrade\r\n"; + $out .= "Sec-WebSocket-Key: $key\r\n"; + $out .= "Sec-WebSocket-Protocol: wamp\r\n"; + $out .= "Sec-WebSocket-Version: 13\r\n"; + $out .= "Origin: {$this->origin}\r\n\r\n"; + + fwrite($this->socket, $out); + + return fgets($this->socket); + } + + /** + * @param string|false $response Response body from the upgrade request or boolean false on failure + * + * @throws BadResponseException if an invalid response was received + */ + private function verifyResponse($response): void + { + if (false === $response) { + if (null !== $this->logger) { + $this->logger->error('WAMP Server did not respond properly'); + } + + throw new BadResponseException('WAMP Server did not respond properly'); + } + + $responseStatus = substr($response, 0, 12); + + if ('HTTP/1.1 101' !== $responseStatus) { + if (null !== $this->logger) { + $this->logger->error('Unexpected HTTP response from WAMP server.', ['response' => $response]); + } + + throw new BadResponseException(sprintf('Unexpected response status. Expected "HTTP/1.1 101", got "%s".', $responseStatus)); + } + } + + /** + * Read the buffer and return the oldest event in stack. + * + * @see https://tools.ietf.org/html/rfc6455#section-5.2 + * + * @throws BadResponseException if the buffer could not be read + */ + private function read(): string + { + $streamBody = stream_get_contents($this->socket, stream_get_meta_data($this->socket)['unread_bytes']); + + if (false === $streamBody) { + if (null !== $this->logger) { + $this->logger->error('The stream buffer could not be read.', ['error' => error_get_last()]); + } + + throw new BadResponseException('The stream buffer could not be read.'); + } + + $startPos = strpos($streamBody, '['); + $endPos = strpos($streamBody, ']'); + + if (false === $startPos || false === $endPos) { + if (null !== $this->logger) { + $this->logger->error('Could not extract response body from stream.', ['body' => $streamBody]); + } + + throw new BadResponseException('Could not extract response body from stream.'); + } + + return substr( + $streamBody, + $startPos, + $endPos + ); + } + + /** + * @throws WebsocketException if the connection could not be disconnected cleanly + */ + public function disconnect(): bool + { + if (false === $this->connected) { + return true; + } + + if (null === $this->socket) { + return true; + } + + $this->send($this->payloadGenerator->generateClosePayload(), WebsocketPayload::OPCODE_CLOSE); + + $firstByte = fread($this->socket, 1); + + if (false === $firstByte) { + if (null !== $this->logger) { + $this->logger->error('Could not extract the payload from the buffer.', ['error' => error_get_last()]); + } + + throw new WebsocketException('Could not extract the payload from the buffer.'); + } + + /** @phpstan-var int<0, 255> $payloadLength */ + $payloadLength = \ord($firstByte); + $payload = fread($this->socket, $payloadLength); + + if (false === $payload) { + if (null !== $this->logger) { + $this->logger->error('Could not extract the payload from the buffer.', ['error' => error_get_last()]); + } + + throw new WebsocketException('Could not extract the payload from the buffer.'); + } + + if ($payloadLength >= 2) { + $bin = $payload[0].$payload[1]; + $status = bindec(sprintf('%08b%08b', \ord($payload[0]), \ord($payload[1]))); + + $this->send($bin.'Close acknowledged: '.$status, WebsocketPayload::OPCODE_CLOSE); + } + + fclose($this->socket); + $this->connected = false; + + return true; + } + + /** + * @param mixed $data Any JSON encodable data + * + * @throws WebsocketException if the data cannot be encoded properly + */ + private function send($data, int $opcode = WebsocketPayload::OPCODE_TEXT): void + { + if (\is_array($data)) { + $payload = json_encode($data); + + if (false === $payload) { + throw new WebsocketException('The data could not be encoded: '.json_last_error_msg()); + } + } elseif (is_scalar($data)) { + $payload = $data; + } else { + throw new WebsocketException('The data must be an array or a scalar value.'); + } + + $encoded = $this->payloadGenerator->encode( + (new WebsocketPayload()) + ->setOpcode($opcode) + ->setMask(0x1) + ->setPayload($payload) + ); + + // Check if the connection was reset, if so try to reconnect + if (false === @fwrite($this->socket, $encoded)) { + $this->connected = false; + $this->connect($this->target); + + fwrite($this->socket, $encoded); + } + } + + /** + * Establish a prefix on server. + * + * @see http://wamp.ws/spec#prefix_message + */ + public function prefix(string $prefix, string $uri): void + { + if (null !== $this->logger) { + $this->logger->info(sprintf('Establishing prefix "%s" for URI "%s"', $prefix, $uri)); + } + + $this->send([Protocol::MSG_PREFIX, $prefix, $uri]); + } + + /** + * Call a procedure on server. + * + * @see http://wamp.ws/spec#call_message + * + * @param array|mixed $args Arguments for the message either as an array or variadic set of parameters + */ + public function call(string $procUri, $args): void + { + if (!\is_array($args)) { + $args = \func_get_args(); + array_shift($args); + } + + if (null !== $this->logger) { + $this->logger->info( + sprintf('Websocket client calling %s', $procUri), + [ + 'callArguments' => $args, + ] + ); + } + + $this->send( + array_merge( + [Protocol::MSG_CALL, uniqid('', true), $procUri], + $args + ) + ); + } + + /** + * The client will send an event to all clients connected to the server who have subscribed to the topicURI. + * + * @see http://wamp.ws/spec#publish_message + * + * @param string[] $exclude + * @param string[] $eligible + */ + public function publish(string $topicUri, string $payload, array $exclude = [], array $eligible = []): void + { + if (null !== $this->logger) { + $this->logger->info( + sprintf('Websocket client publishing to %s', $topicUri), + [ + 'payload' => $payload, + 'excludedIds' => $exclude, + 'eligibleIds' => $eligible, + ] + ); + } + + $this->send([Protocol::MSG_PUBLISH, $topicUri, $payload, $exclude, $eligible]); + } + + /** + * Subscribers receive PubSub events published by subscribers via the EVENT message. The EVENT message contains the topicURI, the topic under which the event was published, and event, the PubSub event payload. + */ + public function event(string $topicUri, string $payload): void + { + if (null !== $this->logger) { + $this->logger->info( + sprintf('Websocket client sending event to %s', $topicUri), + [ + 'payload' => $payload, + ] + ); + } + + $this->send([Protocol::MSG_EVENT, $topicUri, $payload]); + } + + private function generateKey(int $length = 16): string + { + $c = 0; + $tmp = ''; + + while ($c++ * 16 < $length) { + $tmp .= md5((string) mt_rand(), true); + } + + return base64_encode(substr($tmp, 0, $length)); + } + + public function isConnected(): bool + { + return $this->connected; + } +} diff --git a/src/Component/WebSocketClient/Wamp/ClientFactory.php b/src/Component/WebSocketClient/Wamp/ClientFactory.php new file mode 100644 index 0000000..27b31f6 --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/ClientFactory.php @@ -0,0 +1,81 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class ClientFactory implements ClientFactoryInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var array + */ + private $config; + + public function __construct(array $config) + { + $this->config = $this->resolveConfig($config); + } + + public function createConnection(): ClientInterface + { + $client = new Client( + $this->config['host'], + $this->config['port'], + $this->config['ssl'], + $this->config['origin'] + ); + + if (null !== $this->logger) { + $client->setLogger($this->logger); + } + + return $client; + } + + private function resolveConfig(array $config): array + { + $resolver = new OptionsResolver(); + + $resolver->setRequired( + [ + 'host', + 'port', + ] + ); + + $resolver->setDefaults( + [ + 'ssl' => false, + 'origin' => null, + ] + ); + + $resolver->setAllowedTypes('host', 'string'); + $resolver->setAllowedTypes('port', ['string', 'integer']); + $resolver->setAllowedTypes('ssl', 'boolean'); + $resolver->setAllowedTypes('origin', ['string', 'null']); + + return $resolver->resolve($config); + } +} diff --git a/src/Component/WebSocketClient/Wamp/ClientFactoryInterface.php b/src/Component/WebSocketClient/Wamp/ClientFactoryInterface.php new file mode 100644 index 0000000..377de1f --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/ClientFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +interface ClientFactoryInterface +{ + public function createConnection(): ClientInterface; +} diff --git a/src/Component/WebSocketClient/Wamp/ClientInterface.php b/src/Component/WebSocketClient/Wamp/ClientInterface.php new file mode 100644 index 0000000..f2a9646 --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/ClientInterface.php @@ -0,0 +1,72 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +interface ClientInterface +{ + /** + * @return string The session identifier for the connection + * + * @throws BadResponseException if a response could not be received from the websocket server + * @throws WebsocketException if the target URI is invalid + */ + public function connect(string $target = '/'): string; + + /** + * @throws WebsocketException if the connection could not be disconnected cleanly + */ + public function disconnect(): bool; + + public function isConnected(): bool; + + /** + * Establish a prefix on server. + * + * @see http://wamp.ws/spec#prefix_message + */ + public function prefix(string $prefix, string $uri): void; + + /** + * Call a procedure on server. + * + * @see http://wamp.ws/spec#call_message + * + * @param array|mixed $args Arguments for the message either as an array or variadic set of parameters + */ + public function call(string $procUri, $args): void; + + /** + * The client will send an event to all clients connected to the server who have subscribed to the topicURI. + * + * @see http://wamp.ws/spec#publish_message + * + * @param string[] $exclude + * @param string[] $eligible + */ + public function publish(string $topicUri, string $payload, array $exclude = [], array $eligible = []): void; + + /** + * Subscribers receive PubSub events published by subscribers via the EVENT message. The EVENT message contains the topicURI, the topic under which the event was published, and event, the PubSub event payload. + */ + public function event(string $topicUri, string $payload): void; +} diff --git a/src/Component/WebSocketClient/Wamp/PayloadGenerator.php b/src/Component/WebSocketClient/Wamp/PayloadGenerator.php new file mode 100644 index 0000000..f7a503c --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/PayloadGenerator.php @@ -0,0 +1,74 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class PayloadGenerator implements PayloadGeneratorInterface +{ + public function encode(WebsocketPayload $websocketPayload): string + { + $payload = $websocketPayload->getFin() << 1 | $websocketPayload->getRsv1(); + $payload = $payload << 1 | $websocketPayload->getRsv2(); + $payload = $payload << 1 | $websocketPayload->getRsv3(); + $payload = $payload << 4 | $websocketPayload->getOpcode(); + $payload = $payload << 1 | $websocketPayload->getMask(); + + if ($websocketPayload->getLength() <= 125) { + $payload = $payload << 7 | $websocketPayload->getLength(); + $payload = pack('n', $payload); + } elseif ($websocketPayload->getLength() <= 0xffff) { + $payload = $payload << 7 | 126; + $payload = pack('n', $payload).pack('n*', $websocketPayload->getLength()); + } else { + $payload = $payload << 7 | 127; + $payload = pack('n', $payload).pack('NN', ($websocketPayload->getLength() & 0xffffffff00000000) >> 32, $websocketPayload->getLength() & 0x00000000ffffffff); + } + + if (0x1 == $websocketPayload->getMask()) { + $payload .= $websocketPayload->getMaskKey(); + $data = $this->maskData($websocketPayload->getPayload(), $websocketPayload->getMaskKey()); + } else { + $data = $websocketPayload->getPayload(); + } + + return $payload.$data; + } + + public function generateClosePayload(): string + { + $str = ''; + + foreach (str_split(sprintf('%016b', 1000), 8) as $binstr) { + $str .= \chr((int) bindec($binstr)); + } + + return $str.'ttfn'; + } + + private function maskData(?string $data, ?string $key): string + { + $masked = ''; + + for ($i = 0; $i < \strlen($data); ++$i) { + $masked .= $data[$i] ^ $key[$i % 4]; + } + + return $masked; + } +} diff --git a/src/Component/WebSocketClient/Wamp/PayloadGeneratorInterface.php b/src/Component/WebSocketClient/Wamp/PayloadGeneratorInterface.php new file mode 100644 index 0000000..c314eb7 --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/PayloadGeneratorInterface.php @@ -0,0 +1,27 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +interface PayloadGeneratorInterface +{ + public function encode(WebsocketPayload $websocketPayload): string; + + public function generateClosePayload(): string; +} diff --git a/src/Component/WebSocketClient/Wamp/Protocol.php b/src/Component/WebSocketClient/Wamp/Protocol.php new file mode 100644 index 0000000..ae7906a --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/Protocol.php @@ -0,0 +1,37 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class Protocol +{ + public const MSG_WELCOME = 0; + public const MSG_PREFIX = 1; + public const MSG_CALL = 2; + public const MSG_CALL_RESULT = 3; + public const MSG_CALL_ERROR = 4; + public const MSG_SUBSCRIBE = 5; + public const MSG_UNSUBSCRIBE = 6; + public const MSG_PUBLISH = 7; + public const MSG_EVENT = 8; + + private function __construct() + { + } +} diff --git a/src/Component/WebSocketClient/Wamp/WebsocketPayload.php b/src/Component/WebSocketClient/Wamp/WebsocketPayload.php new file mode 100644 index 0000000..30b3436 --- /dev/null +++ b/src/Component/WebSocketClient/Wamp/WebsocketPayload.php @@ -0,0 +1,198 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ +final class WebsocketPayload +{ + public const OPCODE_CONTINUE = 0x0; + public const OPCODE_TEXT = 0x1; + public const OPCODE_BINARY = 0x2; + public const OPCODE_NON_CONTROL_RESERVED_1 = 0x3; + public const OPCODE_NON_CONTROL_RESERVED_2 = 0x4; + public const OPCODE_NON_CONTROL_RESERVED_3 = 0x5; + public const OPCODE_NON_CONTROL_RESERVED_4 = 0x6; + public const OPCODE_NON_CONTROL_RESERVED_5 = 0x7; + public const OPCODE_CLOSE = 0x8; + public const OPCODE_PING = 0x9; + public const OPCODE_PONG = 0xA; + public const OPCODE_CONTROL_RESERVED_1 = 0xB; + public const OPCODE_CONTROL_RESERVED_2 = 0xC; + public const OPCODE_CONTROL_RESERVED_3 = 0xD; + public const OPCODE_CONTROL_RESERVED_4 = 0xE; + public const OPCODE_CONTROL_RESERVED_5 = 0xF; + + /** + * @var int + */ + private $fin = 0x1; + + /** + * @var int + */ + private $rsv1 = 0x0; + + /** + * @var int + */ + private $rsv2 = 0x0; + + /** + * @var int + */ + private $rsv3 = 0x0; + + /** + * @var int|null + */ + private $opcode; + + /** + * @var int + */ + private $mask = 0x0; + + /** + * @var string + */ + private $maskKey; + + /** + * @var mixed + */ + private $payload; + + public function setFin(int $fin): self + { + $this->fin = $fin; + + return $this; + } + + public function getFin(): int + { + return $this->fin; + } + + public function setRsv1(int $rsv1): self + { + $this->rsv1 = $rsv1; + + return $this; + } + + public function getRsv1(): int + { + return $this->rsv1; + } + + public function setRsv2(int $rsv2): self + { + $this->rsv2 = $rsv2; + + return $this; + } + + public function getRsv2(): int + { + return $this->rsv2; + } + + public function setRsv3(int $rsv3): self + { + $this->rsv3 = $rsv3; + + return $this; + } + + public function getRsv3(): int + { + return $this->rsv3; + } + + public function setOpcode(?int $opcode): self + { + $this->opcode = $opcode; + + return $this; + } + + public function getOpcode(): ?int + { + return $this->opcode; + } + + public function setMask(int $mask): self + { + $this->mask = $mask; + + if (true == $this->mask) { + $this->generateMaskKey(); + } + + return $this; + } + + public function getMask(): int + { + return $this->mask; + } + + public function getLength(): int + { + return \strlen($this->getPayload()); + } + + public function setMaskKey(string $maskKey): self + { + $this->maskKey = $maskKey; + + return $this; + } + + public function getMaskKey(): ?string + { + return $this->maskKey; + } + + /** + * @param mixed $payload + */ + public function setPayload($payload): self + { + $this->payload = $payload; + + return $this; + } + + /** + * @return mixed + */ + public function getPayload() + { + return $this->payload; + } + + public function generateMaskKey(): string + { + $this->setMaskKey(random_bytes(4)); + + return $this->getMaskKey(); + } +} From 31a4af4321ba9d7aa4fb6f66398f520a465fdc26 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubrovskyi Date: Tue, 9 Jun 2026 13:24:30 +0200 Subject: [PATCH 6/6] Update version for proget complince --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 80a83ca..106fa41 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^8.4", "cboden/ratchet": "^0.4", - "oroinc/pubsub-router-bundle": "2.9.x-dev", + "oroinc/pubsub-router-bundle": "~2.9", "psr/log": "^1.1 || ^2.0 || ^3.0", "react/event-loop": "^1.2", "react/socket": "^1.9",