Skip to content

Commit

Permalink
Inject SW using the pwa Twig function
Browse files Browse the repository at this point in the history
  • Loading branch information
Spomky committed Jan 21, 2024
1 parent 81042eb commit e2fb914
Showing 1 changed file with 110 additions and 20 deletions.
130 changes: 110 additions & 20 deletions src/Twig/PwaRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@

namespace SpomkyLabs\PwaBundle\Twig;

use InvalidArgumentException;
use SpomkyLabs\PwaBundle\Dto\Icon;
use SpomkyLabs\PwaBundle\Dto\Manifest;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\ImportMap\ImportMapConfigReader;
use Symfony\Component\AssetMapper\MappedAsset;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Mime\MimeTypes;
use const ENT_COMPAT;
use const ENT_SUBSTITUTE;
use const PHP_EOL;

final readonly class PwaRuntime
{
private string $manifestPublicUrl;

public function __construct(
#[Autowire('@asset_mapper.importmap.config_reader')]
private ImportMapConfigReader $importMapConfigReader,
private AssetMapperInterface $assetMapper,
private Manifest $manifest,
#[Autowire('%spomky_labs_pwa.manifest_public_url%')]
Expand All @@ -25,28 +31,86 @@ public function __construct(
$this->manifestPublicUrl = '/' . trim($manifestPublicUrl, '/');
}

public function load(bool $themeColor = true, bool $icons = true): string
{
/**
* @param array<string, mixed> $swAttributes
*/
public function load(
bool $injectThemeColor = true,
bool $injectIcons = true,
bool $injectSW = true,
array $swAttributes = []
): string {
$url = $this->assetMapper->getPublicPath($this->manifestPublicUrl) ?? $this->manifestPublicUrl;
$output = sprintf('<link rel="manifest" href="%s">', $url);
if ($this->manifest->icons !== [] && $icons === true) {
foreach ($this->manifest->icons as $icon) {
['url' => $url, 'format' => $format] = $this->getIconInfo($icon);
$attributes = sprintf(
'rel="%s" sizes="%s" href="%s"',
str_contains($icon->purpose ?? '', 'maskable') ? 'mask-icon' : 'icon',
$icon->getSizeList(),
$url
);
if ($format !== null) {
$attributes .= sprintf(' type="%s"', $format);
}

$output .= sprintf('%s<link %s>', PHP_EOL, $attributes);
}
$output = sprintf('%s<link rel="manifest" href="%s">', PHP_EOL, $url);
$output = $this->injectIcons($output, $injectIcons);
$output = $this->injectThemeColor($output, $injectThemeColor);

return $this->injectServiceWorker($output, $injectSW, $swAttributes);
}

private function injectThemeColor(string $output, bool $themeColor): string
{
if ($this->manifest->themeColor === null || $themeColor === false) {
return $output;
}

return $output . sprintf('%s<meta name="theme-color" content="%s">', PHP_EOL, $this->manifest->themeColor);
}

/**
* @param array<string, mixed> $swAttributes
*/
private function injectServiceWorker(string $output, bool $injectSW, array $swAttributes): string
{
$serviceWorker = $this->manifest->serviceWorker;
if ($serviceWorker === null || $injectSW === false) {
return $output;
}
$scriptAttributes = $this->createAttributesString($swAttributes);
$url = $serviceWorker->dest;
$registerOptions = '';
if ($serviceWorker->scope !== null) {
$registerOptions .= sprintf(", scope: '%s'", $serviceWorker->scope);
}
if ($serviceWorker->useCache !== null) {
$registerOptions .= sprintf(', useCache: %s', $serviceWorker->useCache ? 'true' : 'false');
}
if ($this->manifest->themeColor !== null && $themeColor === true) {
$output .= sprintf('%s<meta name="theme-color" content="%s">', PHP_EOL, $this->manifest->themeColor);
if ($registerOptions !== '') {
$registerOptions = sprintf(', {%s}', mb_substr($registerOptions, 2));
}
$hasWorkboxWindow = $this->importMapConfigReader->findRootImportMapEntry('workbox-window') !== null;
$workboxUrl = $hasWorkboxWindow ? 'workbox-window' : 'https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-window.prod.mjs';
$declaration = <<<SERVICE_WORKER
<script type="module" {$scriptAttributes}>
import {Workbox} from '{$workboxUrl}';
if ('serviceWorker' in navigator) {
const wb = new Workbox('{$url}'{$registerOptions});
wb.register();
}
</script>
SERVICE_WORKER;

return $output . sprintf('%s%s', PHP_EOL, $declaration);
}

private function injectIcons(string $output, bool $injectIcons): string
{
if ($this->manifest->icons === [] || $injectIcons === false) {
return $output;
}
foreach ($this->manifest->icons as $icon) {
['url' => $url, 'format' => $format] = $this->getIconInfo($icon);
$attributes = sprintf(
'rel="%s" sizes="%s" href="%s"',
str_contains($icon->purpose ?? '', 'maskable') ? 'mask-icon' : 'icon',
$icon->getSizeList(),
$url
);
if ($format !== null) {
$attributes .= sprintf(' type="%s"', $format);
}

$output .= sprintf('%s<link %s>', PHP_EOL, $attributes);
}

return $output;
Expand Down Expand Up @@ -87,4 +151,30 @@ private function getFormat(Icon $object, ?MappedAsset $asset): ?string
$mime = MimeTypes::getDefault();
return $mime->guessMimeType($asset->sourcePath);
}

private function createAttributesString(array $attributes): string
{
$attributeString = '';
if (isset($attributes['src']) || isset($attributes['type'])) {
throw new InvalidArgumentException(sprintf(
'The "src" and "type" attributes are not allowed on the <script> tag rendered by "%s".',
self::class
));
}
foreach ($attributes as $name => $value) {
$attributeString .= ' ';
if ($value === true) {
$attributeString .= $name;

continue;
}
$attributeString .= sprintf(
'%s="%s"',
$name,
htmlspecialchars((string) $value, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')
);
}

return $attributeString;
}
}

0 comments on commit e2fb914

Please sign in to comment.