Skip to content

Commit

Permalink
Add background fetch support to service worker
Browse files Browse the repository at this point in the history
Introduce DTO, JS controller, and service worker rule to handle background fetch functionality. This includes configuration options and UI updates for progress, success, and failure states, enabling dynamic service worker behavior.
  • Loading branch information
Spomky committed Feb 14, 2025
1 parent 995cd68 commit 28663a4
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 10 deletions.
7 changes: 7 additions & 0 deletions assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
"version": "1.0.0",
"symfony": {
"controllers": {
"backgroundfetch": {
"main": "src/backgroundfetch_controller.js",
"name": "pwa/backgroundfetch",
"webpackMode": "eager",
"fetch": "eager",
"enabled": true
},
"backgroundsync-form": {
"main": "src/backgroundsync-form_controller.js",
"name": "pwa/backgroundsync-form",
Expand Down
93 changes: 93 additions & 0 deletions assets/src/backgroundfetch_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';

import Controller from './abstract_controller.js';

/* stimulusFetch: 'lazy' */
export default class extends Controller {
static values = {
id: { type: String },
url: { type: String },
cacheName: { type: String, default: null },
persistentStorage: { type: Boolean, default: false },
title: { type: String, default: undefined },
downloadTotal: { type: Number, default: undefined },
icons: { type: String, default: undefined },
};
cache = null;

connect = async () => {
if (!this.idValue) {
console.error("No ID provided");
return;
}
if (!this.urlValue) {
console.error("No URL provided");
return;
}
if (this.persistentStorageValue && navigator.storage && navigator.storage.persist) {
navigator.storage.persist();
}
if (this.cacheNameValue) {
this.cache = await caches.open(this.cacheNameValue);
}
await this.state();
}

state = async () => {
const payload = {
id: this.idValue,
url: this.urlValue,
}
if (!this.cache) {
this.dispatchEvent('missing', payload);
return 'missing';
}
const isInCache = await this.cache.match(this.urlValue);
if (!isInCache) {
this.dispatchEvent('missing', payload);
return 'missing';
}
this.dispatchEvent('cached', payload);
return 'cached';
}

download = async ({params}) => {
const state = await this.state();
if (state === 'cached' && !params.force) {
return;
}
const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.fetch(this.idValue, [this.urlValue], {
title: this.titleValue,
icons: JSON.parse(this.iconsValue ??'[]'),
downloadTotal: this.downloadTotalValue,
});
bgFetch.addEventListener('progress', () => this.dispatchStatus(bgFetch));
}

dispatchStatus = async (bgFetch) => {
this.dispatchEvent('progress', {
id: bgFetch.id,
uploadTotal: bgFetch.uploadTotal,
uploaded: bgFetch.uploaded,
downloadTotal: bgFetch.downloadTotal,
downloaded: bgFetch.downloaded,
result: bgFetch.result,
failureReason: bgFetch.failureReason,
recordsAvailable: bgFetch.recordsAvailable,
});
if (!bgFetch.recordsAvailable || !this.cache) {
return;
}
const records = await bgFetch.matchAll();
const promises = records.map(async record => {
const response = await record.responseReady;
await this.cache.put(record.request, response);
this.dispatchEvent('cached', {
id: this.idValue,
url: this.urlValue,
});
});
await Promise.all(promises);
}
}
80 changes: 70 additions & 10 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,18 @@ parameters:
count: 1
path: src/DataCollector/PwaCollector.php

-
message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch has an uninitialized property \$cacheName\. Give it default value or assign it in the constructor\.$#'
identifier: property.uninitialized
count: 1
path: src/Dto/BackgroundFetch.php

-
message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch has an uninitialized property \$enabled\. Give it default value or assign it in the constructor\.$#'
identifier: property.uninitialized
count: 1
path: src/Dto/BackgroundFetch.php

-
message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\BackgroundSync has an uninitialized property \$forceSyncFallback\. Give it default value or assign it in the constructor\.$#'
identifier: property.uninitialized
Expand Down Expand Up @@ -1479,13 +1491,13 @@ parameters:
-
message: '#^Cannot call method append\(\) on mixed\.$#'
identifier: method.nonObject
count: 6
count: 8
path: src/Resources/config/definition/service_worker.php

-
message: '#^Cannot call method arrayNode\(\) on mixed\.$#'
identifier: method.nonObject
count: 17
count: 18
path: src/Resources/config/definition/service_worker.php

-
Expand Down Expand Up @@ -1515,7 +1527,7 @@ parameters:
-
message: '#^Cannot call method canBeEnabled\(\) on mixed\.$#'
identifier: method.nonObject
count: 1
count: 2
path: src/Resources/config/definition/service_worker.php

-
Expand All @@ -1527,7 +1539,7 @@ parameters:
-
message: '#^Cannot call method children\(\) on mixed\.$#'
identifier: method.nonObject
count: 11
count: 12
path: src/Resources/config/definition/service_worker.php

-
Expand All @@ -1539,7 +1551,7 @@ parameters:
-
message: '#^Cannot call method defaultNull\(\) on mixed\.$#'
identifier: method.nonObject
count: 6
count: 8
path: src/Resources/config/definition/service_worker.php

-
Expand All @@ -1557,13 +1569,13 @@ parameters:
-
message: '#^Cannot call method end\(\) on mixed\.$#'
identifier: method.nonObject
count: 107
count: 112
path: src/Resources/config/definition/service_worker.php

-
message: '#^Cannot call method example\(\) on mixed\.$#'
identifier: method.nonObject
count: 33
count: 36
path: src/Resources/config/definition/service_worker.php

-
Expand All @@ -1587,7 +1599,7 @@ parameters:
-
message: '#^Cannot call method info\(\) on mixed\.$#'
identifier: method.nonObject
count: 68
count: 71
path: src/Resources/config/definition/service_worker.php

-
Expand All @@ -1605,7 +1617,7 @@ parameters:
-
message: '#^Cannot call method isRequired\(\) on mixed\.$#'
identifier: method.nonObject
count: 5
count: 6
path: src/Resources/config/definition/service_worker.php

-
Expand All @@ -1617,7 +1629,7 @@ parameters:
-
message: '#^Cannot call method scalarNode\(\) on mixed\.$#'
identifier: method.nonObject
count: 37
count: 40
path: src/Resources/config/definition/service_worker.php

-
Expand Down Expand Up @@ -1872,6 +1884,54 @@ parameters:
count: 1
path: src/ServiceWorkerRule/AppendCacheStrategies.php

-
message: '#^Cannot access property \$cacheName on SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch\|null\.$#'
identifier: property.nonObject
count: 1
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Cannot access property \$enabled on SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch\|null\.$#'
identifier: property.nonObject
count: 1
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Cannot access property \$failureMessage on SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch\|null\.$#'
identifier: property.nonObject
count: 2
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Cannot access property \$progressUrl on SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch\|null\.$#'
identifier: property.nonObject
count: 4
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Cannot access property \$successMessage on SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch\|null\.$#'
identifier: property.nonObject
count: 2
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Cannot access property \$successUrl on SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch\|null\.$#'
identifier: property.nonObject
count: 4
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\BackgroundFetchCache\:\:__construct$#'
identifier: shipmonk.deadMethod
count: 1
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\BackgroundFetchCache\:\:getPriority$#'
identifier: shipmonk.deadMethod
count: 1
path: src/ServiceWorkerRule/BackgroundFetchCache.php

-
message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\ClearCache\:\:__construct$#'
identifier: shipmonk.deadMethod
Expand Down
24 changes: 24 additions & 0 deletions src/Dto/BackgroundFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

Check failure on line 1 in src/Dto/BackgroundFetch.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static Analysis

Ignored error pattern #^Class SpomkyLabs\\PwaBundle\\Dto\\BackgroundFetch has an uninitialized property \$cacheName\. Give it default value or assign it in the constructor\.$# (property.uninitialized) in path /home/runner/work/pwa-bundle/pwa-bundle/src/Dto/BackgroundFetch.php was not matched in reported errors.

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Dto;

use Symfony\Component\Serializer\Attribute\SerializedName;

final class BackgroundFetch
{
public bool $enabled;

#[SerializedName('progress_url')]
public null|Url $progressUrl = null;

#[SerializedName('success_url')]
public null|Url $successUrl = null;

#[SerializedName('success_message')]
public null|string $successMessage = null;

#[SerializedName('failure_message')]
public null|string $failureMessage = null;
}
3 changes: 3 additions & 0 deletions src/Dto/ServiceWorker.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ final class ServiceWorker
public bool $skipWaiting = false;

public Workbox $workbox;

#[SerializedName('background_fetch')]
public null|BackgroundFetch $backgroundFetch = null;
}
17 changes: 17 additions & 0 deletions src/Resources/config/definition/service_worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@
->defaultTrue()
->info('Whether the service worker should use the cache.')
->end()
->arrayNode('background_fetch')
->canBeEnabled()
->children()
->append(getUrlNode('progress_url', 'The URL of the progress page.'))
->append(getUrlNode('success_url', 'The URL of the success page.'))
->scalarNode('success_message')
->info('The message to display on success. This message is translated.')
->defaultNull()
->example(['The download is complete.'])
->end()
->scalarNode('failure_message')
->info('The message to display on success. This message is translated.')
->defaultNull()
->example(['The download is complete.'])
->end()
->end()
->end()
->arrayNode('workbox')
->info('The configuration of the workbox.')
->canBeDisabled()
Expand Down
Loading

0 comments on commit 28663a4

Please sign in to comment.