-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement PWA Collector and additional viewing templates
Implemented a new Progressive Web App (PWA) Collector to fetch and manage service worker and manifest details. Additionally, created multiple HTML templates for manifest, service worker, and other PWA tabs to properly display the collected data in UI.
- Loading branch information
Showing
6 changed files
with
555 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\DataCollector; | ||
|
||
use SpomkyLabs\PwaBundle\CachingStrategy\CacheStrategyInterface; | ||
use SpomkyLabs\PwaBundle\CachingStrategy\HasCacheStrategiesInterface; | ||
use SpomkyLabs\PwaBundle\Dto\Manifest; | ||
use SpomkyLabs\PwaBundle\Dto\ServiceWorker; | ||
use SpomkyLabs\PwaBundle\Dto\Workbox; | ||
use Symfony\Component\DependencyInjection\Attribute\Autowire; | ||
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\DataCollector\DataCollector; | ||
use Symfony\Component\Serializer\Encoder\JsonEncode; | ||
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; | ||
use Symfony\Component\Serializer\SerializerInterface; | ||
use Throwable; | ||
use function count; | ||
use function in_array; | ||
use const JSON_PRETTY_PRINT; | ||
use const JSON_THROW_ON_ERROR; | ||
use const JSON_UNESCAPED_SLASHES; | ||
use const JSON_UNESCAPED_UNICODE; | ||
|
||
final class PwaCollector extends DataCollector | ||
{ | ||
/** | ||
* @param iterable<HasCacheStrategiesInterface> $cachingServices | ||
*/ | ||
public function __construct( | ||
private readonly SerializerInterface $serializer, | ||
#[TaggedIterator('spomky_labs_pwa.cache_strategy')] | ||
private readonly iterable $cachingServices, | ||
private readonly Manifest $manifest, | ||
private readonly ServiceWorker $serviceWorker, | ||
#[Autowire(param: 'spomky_labs_pwa.manifest.enabled')] | ||
private readonly bool $manifestEnabled, | ||
) { | ||
} | ||
|
||
public function collect(Request $request, Response $response, Throwable $exception = null): void | ||
{ | ||
$jsonOptions = [ | ||
AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true, | ||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true, | ||
JsonEncode::OPTIONS => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, | ||
]; | ||
$this->data['cachingStrategies'] = []; | ||
foreach ($this->cachingServices as $cachingService) { | ||
foreach ($cachingService->getCacheStrategies() as $cacheStrategy) { | ||
$this->data['cachingStrategies'][] = $cacheStrategy; | ||
} | ||
} | ||
$this->data['serviceWorker'] = $this->serviceWorker; | ||
$this->data['manifest'] = [ | ||
'enabled' => $this->manifestEnabled, | ||
'data' => $this->manifest, | ||
'installable' => $this->isInstallable(), | ||
'output' => $this->serializer->serialize($this->manifest, 'json', $jsonOptions), | ||
]; | ||
|
||
dump($this->data); | ||
} | ||
|
||
public function getData(): array | ||
{ | ||
return $this->data; | ||
} | ||
|
||
/** | ||
* @return array<CacheStrategyInterface> | ||
*/ | ||
public function getCachingStrategies(): array | ||
{ | ||
return $this->data['cachingStrategies'] ?? []; | ||
} | ||
|
||
public function getManifest(): Manifest | ||
{ | ||
return $this->data['manifest']['data']; | ||
} | ||
|
||
public function getWorkbox(): Workbox | ||
{ | ||
return $this->data['serviceWorker']->workbox; | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return 'pwa'; | ||
} | ||
|
||
/** | ||
* @return array{status: bool, reasons: string[]} | ||
*/ | ||
private function isInstallable(): array | ||
{ | ||
$reasons = [ | ||
'The manifest must be enabled' => ! $this->manifestEnabled, | ||
'The manifest must have a short name or a name' => $this->manifest->shortName === null && $this->manifest->name === null, | ||
'The manifest must have a start URL' => $this->manifest->startUrl === null, | ||
'The manifest must have a display value set to "standalone", "fullscreen" or "minimal-ui' => ! in_array( | ||
$this->manifest->display, | ||
['standalone', 'fullscreen', 'minimal-ui'], | ||
true | ||
), | ||
'The manifest must have at least one icon' => $this->manifest->icons === null || count( | ||
$this->manifest->icons | ||
) === 0, | ||
'The manifest must have the "prefer_related_applications" property set to a value other than "true"' => $this->manifest->preferRelatedApplications === true, | ||
]; | ||
|
||
return [ | ||
Check failure on line 116 in src/DataCollector/PwaCollector.php GitHub Actions / PHP 8.3 Test on ubuntu-latest
|
||
'status' => count(array_filter($reasons)) === 0, | ||
'reasons' => $reasons, | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
<h3>General information</h3> | ||
<p> | ||
Status: | ||
{% if collector.data.manifest.enabled %} | ||
<span class="status-badge status-success">enabled</span> | ||
{% else %} | ||
<span class="status-badge status-warning">disabled</span> | ||
{% endif %} | ||
<br> | ||
Can be installed: | ||
{% if collector.data.manifest.installable.status %} | ||
<span class="status-badge status-success">yes</span> | ||
{% else %} | ||
<span class="status-badge status-warning">no</span> | ||
{% endif %} | ||
</p> | ||
<ul> | ||
{% for reason, value in collector.data.manifest.installable.reasons %} | ||
<li> | ||
{% if not value %} | ||
<span class="badge status-success">success</span> | ||
{% else %} | ||
<span class="badge status-error">failure</span> | ||
{% endif %}: | ||
{{ reason }} | ||
</li> | ||
{% endfor %} | ||
</ul> | ||
<h3>Details</h3> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th scope="col" class="key">Key</th> | ||
<th scope="col">Value</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td>ID</td> | ||
<td>{{ collector.getManifest().id }}</td> | ||
</tr> | ||
<tr> | ||
<td>Name</td> | ||
<td>{{ collector.getManifest().name|trans }}</td> | ||
</tr> | ||
<tr> | ||
<td>Short name</td> | ||
<td>{{ collector.getManifest().shortName|trans }}</td> | ||
</tr> | ||
<tr> | ||
<td>Description name</td> | ||
<td>{{ collector.getManifest().description|trans }}</td> | ||
</tr> | ||
<tr> | ||
<td>Theme color</td> | ||
<td> | ||
{% if collector.getManifest().themeColor %} | ||
{{ collector.getManifest().themeColor }} | ||
<span style="background-color: {{ collector.getManifest().themeColor }}; padding: 0.25rem; color: {{ collector.getManifest().themeColor }};">{{ collector.getManifest().themeColor }}</span> | ||
{% else %} | ||
n/a | ||
{% endif %} | ||
</td> | ||
</tr> | ||
<tr> | ||
<td>Background color</td> | ||
<td> | ||
{% if collector.getManifest().backgroundColor %} | ||
{{ collector.getManifest().backgroundColor }} | ||
<span style="background-color: {{ collector.getManifest().backgroundColor }}; padding: 0.25rem; color: {{ collector.getManifest().backgroundColor }};">{{ collector.getManifest().backgroundColor }}</span> | ||
{% else %} | ||
n/a | ||
{% endif %} | ||
</td> | ||
</tr> | ||
<tr> | ||
<td>Display</td> | ||
<td>{{ collector.getManifest().display }}</td> | ||
</tr> | ||
<tr> | ||
<td>Orientation</td> | ||
<td>{{ collector.getManifest().orientation }}</td> | ||
</tr> | ||
<tr> | ||
<td>Scope</td> | ||
<td>{{ collector.getManifest().scope }}</td> | ||
</tr> | ||
<tr> | ||
<td>Start URL</td> | ||
<td>{{ collector.getManifest().startUrl }}</td> | ||
</tr> | ||
<tr> | ||
<td>Categories</td> | ||
<td> | ||
{% if collector.getManifest().categories|length == 0 %} | ||
<span class="badge">none</span> | ||
{% else %} | ||
<ul> | ||
{% for category in collector.getManifest().categories %} | ||
<li>{{ category|trans }}</li> | ||
{% endfor %} | ||
</ul> | ||
{% endif %} | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<h3 class="tab-title">Application Icons</h3> | ||
<div class="tab-content"> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th scope="col" class="key">Source</th> | ||
<th scope="col">Sizes</th> | ||
<th scope="col">Type</th> | ||
<th scope="col">Purpose</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for icon in collector.getManifest().icons %} | ||
<tr> | ||
<td>{{ icon.src.src }}</td> | ||
<td>{{ icon.getSizeList() }}</td> | ||
<td>{{ icon.type|default('n/a') }}</td> | ||
<td>{{ icon.purpose|default('n/a') }}</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
</div> | ||
<h3 class="tab-title">Application screenshots</h3> | ||
<div class="tab-content"> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th scope="col" class="key">Source</th> | ||
<th scope="col">Size</th> | ||
<th scope="col">Type</th> | ||
<th scope="col">Form factor</th> | ||
<th scope="col">Label</th> | ||
<th scope="col">Platform</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for screenshot in collector.getManifest().screenshots %} | ||
<tr> | ||
<td> | ||
{{ screenshot.src.src }}<br> | ||
<span class="badge">{{ screenshot.reference|default('No reference') }}</span> | ||
</td> | ||
<td> | ||
{% if screenshot.height and screenshot.width %} | ||
{{ screenshot.width }}x{{ screenshot.height }} | ||
{% else %} | ||
n/a | ||
{% endif %} | ||
</td> | ||
<td>{{ screenshot.type|default('n/a') }}</td> | ||
<td>{{ screenshot.formFactor|default('n/a') }}</td> | ||
<td>{{ screenshot.label|default('n/a') }}</td> | ||
<td>{{ screenshot.platform|default('n/a') }}</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
</div> | ||
<h3 class="tab-title">Output</h3> | ||
<div class="tab-content"> | ||
<pre class="output">{{ collector.data.manifest.output|nl2br }}</pre> | ||
</div> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.