From 9f611e453fa5ee5404caf8da484684565e60f305 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sun, 7 Apr 2024 20:44:21 +0200 Subject: [PATCH 1/3] Add support for custom user agent in screenshot generation This update includes a new service, `ScreenshotSubscriber`, that disables profiler if the user agent matches a specified one. Also, a custom user agent can now be used when using the `CreateScreenshotCommand`, and this setting is configurable in `web_client.php`. Finally, some PHPStan warnings were addressed and the "ext-sockets" package was added to the dev dependencies. --- composer.json | 1 + phpstan-baseline.neon | 15 ++++++ src/Command/CreateScreenshotCommand.php | 50 ++++++++++++++++++- src/EventSubscriber/ScreenshotSubscriber.php | 47 +++++++++++++++++ .../config/definition/web_client.php | 6 +++ src/Resources/config/services.php | 2 + src/SpomkyLabsPwaBundle.php | 2 + 7 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/EventSubscriber/ScreenshotSubscriber.php diff --git a/composer.json b/composer.json index bdab78c..03d67db 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "twig/twig": "^3.8" }, "require-dev": { + "ext-sockets": "*", "dbrekelmans/bdi": "^1.1", "infection/infection": "^0.28", "phpstan/extension-installer": "^1.1", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f78b5f7..b640631 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -107,9 +107,24 @@ parameters: - message: "#^Cannot cast mixed to string\\.$#" + count: 2 + path: src/Command/CreateScreenshotCommand.php + + - + message: "#^Method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateScreenshotCommand\\:\\:getDefaultArguments\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Command/CreateScreenshotCommand.php + + - + message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#" count: 1 path: src/Command/CreateScreenshotCommand.php + - + message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" + count: 3 + path: src/Command/CreateScreenshotCommand.php + - message: "#^Parameter \\#1 \\$image of method SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessorInterface\\:\\:getSizes\\(\\) expects string, string\\|false given\\.$#" count: 1 diff --git a/src/Command/CreateScreenshotCommand.php b/src/Command/CreateScreenshotCommand.php index 6a14df5..6d0bcdc 100644 --- a/src/Command/CreateScreenshotCommand.php +++ b/src/Command/CreateScreenshotCommand.php @@ -38,10 +38,22 @@ public function __construct( private readonly null|ImageProcessorInterface $imageProcessor, #[Autowire('@pwa.web_client')] null|Client $webClient = null, + #[Autowire(param: 'spomky_labs_pwa.screenshot_user_agent')] + private readonly null|string $userAgent = null, ) { parent::__construct(); if ($webClient === null) { - $webClient = Client::createChromeClient(); + $options = [ + 'port' => $this->getAvailablePort(), + 'capabilities' => [ + 'acceptInsecureCerts' => true, + ], + ]; + $arguments = $this->getDefaultArguments(); + if ($this->userAgent !== null) { + $arguments[] = sprintf('--user-agent=%s', $this->userAgent); + } + $webClient = Client::createChromeClient(arguments: $arguments, options: $options); } $this->webClient = $webClient; } @@ -168,4 +180,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::SUCCESS; } + + private function getAvailablePort(): int + { + $socket = socket_create_listen(0); + assert($socket !== false, 'Unable to create a socket.'); + socket_getsockname($socket, $address, $port); + socket_close($socket); + + return $port; + } + + private function getDefaultArguments(): array + { + $args = []; + + if (! ($_SERVER['PANTHER_NO_HEADLESS'] ?? false)) { + $args[] = '--headless'; + $args[] = '--window-size=1200,1100'; + $args[] = '--disable-gpu'; + } + + if ($_SERVER['PANTHER_DEVTOOLS'] ?? true) { + $args[] = '--auto-open-devtools-for-tabs'; + } + + if ($_SERVER['PANTHER_NO_SANDBOX'] ?? $_SERVER['HAS_JOSH_K_SEAL_OF_APPROVAL'] ?? false) { + $args[] = '--no-sandbox'; + } + + if ($_SERVER['PANTHER_CHROME_ARGUMENTS'] ?? false) { + $arguments = explode(' ', (string) $_SERVER['PANTHER_CHROME_ARGUMENTS']); + $args = array_merge($args, $arguments); + } + + return $args; + } } diff --git a/src/EventSubscriber/ScreenshotSubscriber.php b/src/EventSubscriber/ScreenshotSubscriber.php new file mode 100644 index 0000000..c164f08 --- /dev/null +++ b/src/EventSubscriber/ScreenshotSubscriber.php @@ -0,0 +1,47 @@ + 'onRequest', + ]; + } + + public function onRequest(RequestEvent $event): void + { + if (! $event->isMainRequest() || $this->profiler === null) { + return; + } + + $userAgent = $event->getRequest() + ->headers->get('user-agent'); + if ($userAgent === null) { + return; + } + $userAgentToFind = $this->userAgent ?? 'HeadlessChrome'; + if (! str_contains($userAgent, $userAgentToFind)) { + return; + } + + $this->profiler->disable(); + } +} diff --git a/src/Resources/config/definition/web_client.php b/src/Resources/config/definition/web_client.php index fae787b..f1673d1 100644 --- a/src/Resources/config/definition/web_client.php +++ b/src/Resources/config/definition/web_client.php @@ -11,5 +11,11 @@ ->defaultNull() ->info('The Panther Client for generating screenshots. If not set, the default client will be used.') ->end() + ->scalarNode('user_agent') + ->defaultNull() + ->info( + 'The user agent to use when generating screenshots. If not set, the default user agent will be used. When requesting the current application in an environment other than "prod", the profiler will be disabled.' + ) + ->end() ->end(); }; diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index fb2a613..f11b9ea 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -12,6 +12,7 @@ use SpomkyLabs\PwaBundle\DataCollector\PwaCollector; use SpomkyLabs\PwaBundle\Dto\Manifest; use SpomkyLabs\PwaBundle\Dto\ServiceWorker; +use SpomkyLabs\PwaBundle\EventSubscriber\ScreenshotSubscriber; use SpomkyLabs\PwaBundle\ImageProcessor\GDImageProcessor; use SpomkyLabs\PwaBundle\ImageProcessor\ImagickImageProcessor; use SpomkyLabs\PwaBundle\MatchCallbackHandler\MatchCallbackHandlerInterface; @@ -142,6 +143,7 @@ '$urls' => abstract_arg('urls'), ]) ; + $container->set(ScreenshotSubscriber::class); if ($configurator->env() !== 'prod') { $container->set(PwaCollector::class) diff --git a/src/SpomkyLabsPwaBundle.php b/src/SpomkyLabsPwaBundle.php index 77e87f4..6dd5843 100644 --- a/src/SpomkyLabsPwaBundle.php +++ b/src/SpomkyLabsPwaBundle.php @@ -37,6 +37,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C if ($config['web_client'] !== null) { $builder->setAlias('pwa.web_client', $config['web_client']); } + $builder->setParameter('spomky_labs_pwa.screenshot_user_agent', $config['user_agent']); + $serviceWorkerConfig = $config['serviceworker']; $manifestConfig = $config['manifest']; if ($serviceWorkerConfig['enabled'] === true && $manifestConfig['enabled'] === true) { From dcff6d96138633ab89fbe0e632f03fa6069a2503 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sun, 7 Apr 2024 20:46:34 +0200 Subject: [PATCH 2/3] Add support for custom user agent in screenshot generation This update includes a new service, `ScreenshotSubscriber`, that disables profiler if the user agent matches a specified one. Also, a custom user agent can now be used when using the `CreateScreenshotCommand`, and this setting is configurable in `web_client.php`. Finally, some PHPStan warnings were addressed and the "ext-sockets" package was added to the dev dependencies. --- src/Command/CreateScreenshotCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Command/CreateScreenshotCommand.php b/src/Command/CreateScreenshotCommand.php index 6d0bcdc..93fbd20 100644 --- a/src/Command/CreateScreenshotCommand.php +++ b/src/Command/CreateScreenshotCommand.php @@ -20,6 +20,7 @@ use Symfony\Component\Panther\Client; use Symfony\Component\Yaml\Yaml; use Throwable; +use function assert; use function count; #[AsCommand( From 5e0c38b1ea098d47c7ddebd2b32577b530a81044 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sun, 7 Apr 2024 21:38:42 +0200 Subject: [PATCH 3/3] Add support for custom user agent in screenshot generation This update includes a new service, `ScreenshotSubscriber`, that disables profiler if the user agent matches a specified one. Also, a custom user agent can now be used when using the `CreateScreenshotCommand`, and this setting is configurable in `web_client.php`. Finally, some PHPStan warnings were addressed and the "ext-sockets" package was added to the dev dependencies. --- src/Command/CreateScreenshotCommand.php | 43 ++++++++++++++----------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Command/CreateScreenshotCommand.php b/src/Command/CreateScreenshotCommand.php index 93fbd20..7d5bdab 100644 --- a/src/Command/CreateScreenshotCommand.php +++ b/src/Command/CreateScreenshotCommand.php @@ -29,8 +29,6 @@ )] final class CreateScreenshotCommand extends Command { - private readonly Client $webClient; - public function __construct( private readonly AssetMapperInterface $assetMapper, private readonly Filesystem $filesystem, @@ -38,25 +36,11 @@ public function __construct( private readonly string $projectDir, private readonly null|ImageProcessorInterface $imageProcessor, #[Autowire('@pwa.web_client')] - null|Client $webClient = null, + private readonly null|Client $webClient = null, #[Autowire(param: 'spomky_labs_pwa.screenshot_user_agent')] private readonly null|string $userAgent = null, ) { parent::__construct(); - if ($webClient === null) { - $options = [ - 'port' => $this->getAvailablePort(), - 'capabilities' => [ - 'acceptInsecureCerts' => true, - ], - ]; - $arguments = $this->getDefaultArguments(); - if ($this->userAgent !== null) { - $arguments[] = sprintf('--user-agent=%s', $this->userAgent); - } - $webClient = Client::createChromeClient(arguments: $arguments, options: $options); - } - $this->webClient = $webClient; } public function isEnabled(): bool @@ -108,12 +92,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int $width = $input->getOption('width'); $format = $input->getOption('format'); - $client = clone $this->webClient; + $client = $this->getClient(); $crawler = $client->request('GET', $url); $tmpName = $this->filesystem ->tempnam('', 'pwa-'); if ($width !== null && $height !== null) { + if ($width < 0 || $height < 0) { + $io->error('Width and height must be positive integers.'); + return self::FAILURE; + } $client->manage() ->window() ->setSize(new WebDriverDimension((int) $width, (int) $height)); @@ -217,4 +205,23 @@ private function getDefaultArguments(): array return $args; } + + private function getClient(): Client + { + if ($this->webClient !== null) { + return clone $this->webClient; + } + $options = [ + 'port' => $this->getAvailablePort(), + 'capabilities' => [ + 'acceptInsecureCerts' => true, + ], + ]; + $arguments = $this->getDefaultArguments(); + if ($this->userAgent !== null) { + $arguments[] = sprintf('--user-agent=%s', $this->userAgent); + } + + return Client::createChromeClient(arguments: $arguments, options: $options); + } }