diff --git a/config/retriex/commerce.yaml b/config/retriex/commerce.yaml index faaf415..4b986d5 100644 --- a/config/retriex/commerce.yaml +++ b/config/retriex/commerce.yaml @@ -3,12 +3,12 @@ parameters: retriex.commerce.enabled: true retriex.commerce.max_shop_results: '%env(SHOPWARE_STORE_API_MAX_RESULT)%' - retriex.commerce.shop_timeout: 5 + retriex.commerce.shop_timeout: 3 retriex.commerce.store_api_base_url: '%env(SHOPWARE_STORE_API_BASE_URL)%' retriex.commerce.sales_channel_access_key: '%env(SHOPWARE_SALES_CHANNEL_ACCESS_KEY)%' retriex.commerce.search_repair.enabled: true - retriex.commerce.search_repair.max_queries: 3 + retriex.commerce.search_repair.max_queries: 2 retriex.commerce.search_repair.min_primary_results_without_repair: 2 # Shop matching and presentation configuration. diff --git a/public/assets/js/base.js b/public/assets/js/base.js index 1592328..cba6c90 100644 --- a/public/assets/js/base.js +++ b/public/assets/js/base.js @@ -386,12 +386,24 @@ document.addEventListener('DOMContentLoaded', () => { const source = new EventSource(`/ask-sse/${encodeURIComponent(jobId)}`); state.eventSource = source; + let networkErrorTimer = null; + + const clearNetworkErrorTimer = () => { + if (!networkErrorTimer) { + return; + } + + clearTimeout(networkErrorTimer); + networkErrorTimer = null; + }; + const complete = () => { if (finished) { return; } finished = true; + clearNetworkErrorTimer(); finishEventStream(); resolve(); }; @@ -402,6 +414,7 @@ document.addEventListener('DOMContentLoaded', () => { } finished = true; + clearNetworkErrorTimer(); finishEventStream(); reject(err); }; @@ -409,12 +422,18 @@ document.addEventListener('DOMContentLoaded', () => { state.completeStream = complete; state.failStream = fail; + source.onopen = () => { + clearNetworkErrorTimer(); + }; + source.onmessage = (event) => { if (state.abortRequested || finished) { complete(); return; } + clearNetworkErrorTimer(); + if (event.data === undefined || event.data === null || event.data === '') { return; } @@ -442,7 +461,18 @@ document.addEventListener('DOMContentLoaded', () => { return; } - fail(new Error('EventSource connection error')); + if (source.readyState === EventSource.CLOSED) { + fail(new Error('EventSource connection closed')); + return; + } + + if (!networkErrorTimer) { + networkErrorTimer = setTimeout(() => { + if (!finished && !state.abortRequested) { + fail(new Error('EventSource connection error')); + } + }, 20000); + } }); }); } catch (err) { diff --git a/src/Shopware/StoreApiClient.php b/src/Shopware/StoreApiClient.php index 5bf26ed..754f321 100644 --- a/src/Shopware/StoreApiClient.php +++ b/src/Shopware/StoreApiClient.php @@ -49,7 +49,11 @@ final readonly class StoreApiClient 'sw-access-key' => $this->salesChannelAccessKey, ], 'body' => $body, + // Keep Shopware calls bounded. During SSE responses the browser only + // receives data between blocking HTTP calls, so long Store API waits + // can look like a broken stream to proxies or the browser. 'timeout' => $this->timeoutSeconds, + 'max_duration' => max(1, $this->timeoutSeconds + 1), ]); $statusCode = $response->getStatusCode();