Skip to content

Commit

Permalink
Add support for custom user agent in screenshot generation (#166)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Spomky authored Apr 7, 2024
1 parent dc34dc1 commit 9f110f8
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 8 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 15 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 64 additions & 8 deletions src/Command/CreateScreenshotCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\Panther\Client;
use Symfony\Component\Yaml\Yaml;
use Throwable;
use function assert;
use function count;

#[AsCommand(
Expand All @@ -28,22 +29,18 @@
)]
final class CreateScreenshotCommand extends Command
{
private readonly Client $webClient;

public function __construct(
private readonly AssetMapperInterface $assetMapper,
private readonly Filesystem $filesystem,
#[Autowire('%kernel.project_dir%')]
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) {
$webClient = Client::createChromeClient();
}
$this->webClient = $webClient;
}

public function isEnabled(): bool
Expand Down Expand Up @@ -95,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));
Expand Down Expand Up @@ -168,4 +169,59 @@ 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;
}

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);
}
}
47 changes: 47 additions & 0 deletions src/EventSubscriber/ScreenshotSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\EventSubscriber;

use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Profiler\Profiler;

final readonly class ScreenshotSubscriber implements EventSubscriberInterface
{
public function __construct(
#[Autowire(service: 'profiler')]
private ?Profiler $profiler = null,
#[Autowire(param: 'spomky_labs_pwa.screenshot_user_agent')]
private null|string $userAgent = null,
) {
}

public static function getSubscribedEvents(): array
{
return [
RequestEvent::class => '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();
}
}
6 changes: 6 additions & 0 deletions src/Resources/config/definition/web_client.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};
2 changes: 2 additions & 0 deletions src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -142,6 +143,7 @@
'$urls' => abstract_arg('urls'),
])
;
$container->set(ScreenshotSubscriber::class);

if ($configurator->env() !== 'prod') {
$container->set(PwaCollector::class)
Expand Down
2 changes: 2 additions & 0 deletions src/SpomkyLabsPwaBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 9f110f8

Please sign in to comment.