Skip to content

Commit

Permalink
Add error handling and response expectation options to BackgroundSync (
Browse files Browse the repository at this point in the history
…#242)

Added configurations for handling 4xx and 5xx errors, expected status codes, and redirect responses in the BackgroundSync functionality. Updated the associated DTO and classes to support these new options, enhancing the robustness and flexibility of background synchronization.
  • Loading branch information
Spomky authored Oct 5, 2024
1 parent 2d93277 commit 60444f3
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 17 deletions.
26 changes: 16 additions & 10 deletions assets/src/backgroundsync-form_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,22 @@ export default class extends Controller {
try {
const params = this.paramsValue;
params.headers = this.headersValue;
if (form.enctype === 'multipart/form-data') {
params.body = new FormData(form);
} else if (form.enctype === 'application/json') {
params.body = JSON.stringify(Object.fromEntries(new FormData(form)));
} else if (form.enctype === 'application/x-www-form-urlencoded') {
params.headers['Content-Type'] = 'application/x-www-form-urlencoded';
params.body = new URLSearchParams(new FormData(form));
} else {
// Unsupported form enctype
return;
switch (form.enctype) {
case 'multipart/form-data':
params.headers['Content-Type'] = 'multipart/form-data';
params.body = new FormData(form);
break;
case 'application/json':
params.headers['Content-Type'] = 'application/json';
params.body = JSON.stringify(Object.fromEntries(new FormData(form)));
break;
case 'application/x-www-form-urlencoded':
params.headers['Content-Type'] = 'application/x-www-form-urlencoded';
params.body = (new URLSearchParams(new FormData(form))).toString();
break;
default:
console.error('Unknown form enctype');
return;
}
params.method = form.method.toUpperCase();
const response = await fetch(url, params);
Expand Down
6 changes: 5 additions & 1 deletion src/CachingStrategy/BackgroundSync.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ public function getCacheStrategies(): array
$sync->queueName,
$sync->maxRetentionTime,
$sync->forceSyncFallback,
$sync->broadcastChannel
$sync->broadcastChannel,
$sync->errorOn4xx,
$sync->errorOn5xx,
$sync->expectRedirect,
$sync->expectedStatusCodes,
),
)
->withMethod($sync->method);
Expand Down
15 changes: 15 additions & 0 deletions src/Dto/BackgroundSync.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ final class BackgroundSync extends Cache
#[SerializedName('match_callback')]
public string $matchCallback;

#[SerializedName('error_on_4xx')]
public bool $errorOn4xx = false;

#[SerializedName('error_on_5xx')]
public bool $errorOn5xx = true;

#[SerializedName('expect_redirect')]
public bool $expectRedirect = false;

/**
* @var array<int>
*/
#[SerializedName('expected_status_codes')]
public array $expectedStatusCodes = [];

public string $method;

#[SerializedName('max_retention_time')]
Expand Down
24 changes: 24 additions & 0 deletions src/Resources/config/definition/service_worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@
->treatTrueLike([])
->info('The background sync configuration.')
->arrayPrototype()
->addDefaultsIfNotSet()
->children()
->scalarNode('queue_name')
->isRequired()
Expand All @@ -404,6 +405,29 @@
->info('The regex or callback function to match the URLs.')
->example(['/\/api\//'])
->end()
->booleanNode('error_on_4xx')
->defaultTrue()
->info('Whether to retry the request on 4xx errors.')
->end()
->booleanNode('error_on_5xx')
->defaultTrue()
->info('Whether to retry the request on 5xx errors.')
->end()
->arrayNode('expected_status_codes')
->treatNullLike([])
->treatFalseLike([])
->treatTrueLike([])
->info(
'The expected success status codes. If the response status code is not in the list, the request will be retried.'
)
->integerPrototype()->end()
->end()
->booleanNode('expect_redirect')
->defaultFalse()
->info(
'Whether to expect a redirect (JS response type should be "opaqueredirect" or the "redirected" property is "true").'
)
->end()
->scalarNode('method')
->defaultValue('POST')
->info('The HTTP method.')
Expand Down
87 changes: 81 additions & 6 deletions src/WorkboxPlugin/BackgroundSyncPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@

namespace SpomkyLabs\PwaBundle\WorkboxPlugin;

use function count;

final readonly class BackgroundSyncPlugin implements CachePluginInterface, HasDebugInterface
{
private const NAME = 'BackgroundSyncPlugin';

/**
* @param array<int> $expectedStatusCodes
*/
public function __construct(
public string $queueName,
public bool $forceSyncFallback,
public null|string $broadcastChannel,
public int $maxRetentionTime,
public bool $errorOn4xx = false,
public bool $errorOn5xx = true,
public bool $expectRedirect = false,
public array $expectedStatusCodes = [],
) {
}

Expand Down Expand Up @@ -42,23 +51,43 @@ public function render(int $jsonOptions = 0): string
BROADCAST_CHANNEL;
}

$errorOn4xx = $this->getErrorOn4xx();
$errorOn5xx = $this->getErrorOn5xx();
$expectRedirect = $this->getExpectRedirect();
$expectedStatusCodes = $this->getExpectedSuccessStatusCodes();

$declaration = <<<BACKGROUND_SYNC_RULE_STRATEGY
new workbox.backgroundSync.BackgroundSyncPlugin('{$this->queueName}',{
"maxRetentionTime": {$this->maxRetentionTime},
"forceSyncFallback": {$forceSyncFallback}{$broadcastChannelSection}
})
{$errorOn4xx}{$errorOn5xx}{$expectRedirect}{$expectedStatusCodes}new workbox.backgroundSync.BackgroundSyncPlugin('{$this->queueName}',{"maxRetentionTime": {$this->maxRetentionTime}, "forceSyncFallback": {$forceSyncFallback}{$broadcastChannelSection}})
BACKGROUND_SYNC_RULE_STRATEGY;

return trim($declaration);
}

/**
* @param array<int> $expectedStatusCodes
*/
public static function create(
string $queueName,
int $maxRetentionTime,
bool $forceSyncFallback,
null|string $broadcastChannel
null|string $broadcastChannel,
bool $errorOn4xx = false,
bool $errorOn5xx = true,
bool $expectRedirect = false,
array $expectedStatusCodes = [],
): static {
return new self($queueName, $forceSyncFallback, $broadcastChannel, $maxRetentionTime);
return new self(
$queueName,
$forceSyncFallback,
$broadcastChannel,
$maxRetentionTime,
$errorOn4xx,
$errorOn5xx,
$expectRedirect,
$expectedStatusCodes,
);
}

public function getDebug(): array
Expand All @@ -68,6 +97,52 @@ public function getDebug(): array
'forceSyncFallback' => $this->forceSyncFallback,
'broadcastChannel' => $this->broadcastChannel,
'maxRetentionTime' => $this->maxRetentionTime,
'errorOn4xx' => $this->errorOn4xx,
'errorOn5xx' => $this->errorOn5xx,
'expectRedirect' => $this->expectRedirect,
'expectedSuccessStatusCodes' => $this->expectedStatusCodes,
];
}

private function getErrorOn4xx(): string
{
if ($this->errorOn5xx === false) {
return '';
}

return $this->getErrorOn(400);
}

private function getErrorOn5xx(): string
{
if ($this->errorOn5xx === false) {
return '';
}

return $this->getErrorOn(500);
}

private function getErrorOn(int $statusCode): string
{
return "{fetchDidSucceed: ({response}) => {if (response.status >= {$statusCode}) {throw new Error('Server error.');}return response;}},";
}

private function getExpectedSuccessStatusCodes(): string
{
if (count($this->expectedStatusCodes) === 0) {
return '';
}
$codes = implode(',', $this->expectedStatusCodes);

return "{fetchDidSucceed: ({response}) => {if (! [{$codes}].includes(response.status)) {throw new Error('Unexpected response status code. Expected one of [{$codes}]. Got ' + response.status);}return response;}},";
}

private function getExpectRedirect(): string
{
if ($this->expectRedirect === false) {
return '';
}

return "{fetchDidSucceed: ({response}) => {if (response.type !== 'opaqueredirect' || response.redirect !== true) {throw new Error('Expected a redirect response.');}return response;}},";
}
}

0 comments on commit 60444f3

Please sign in to comment.