From 05afc49ea64c874e0ea296b5d5d4b49bb82c6d6e Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:50:29 +0200 Subject: [PATCH] Fix front-component cache read rejection bypassing network fallback (#22785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Quick follow-up to #22672. `frontComponentCacheStorageService.read` returned `cachedResponse.text()` without awaiting it, so the promise escaped the surrounding `try/catch`. A cache entry with an unreadable body (corrupt or partially-evicted `CacheStorage` entry) would reject in `fetchComponentSource` and break component rendering entirely, instead of being treated as a cache miss with a network fallback. - `await` the body read inside the `try/catch` so decoding failures degrade to a network fetch - Add a regression test: cached body read rejects → source is still served from the network ## Test plan - `fetchComponentSource.spec.ts` — new test `falls back to the network when the cached response body is unreadable`; full renderer suite passes (14 tests) Made with [Cursor](https://cursor.com) Review in cubic --------- Co-authored-by: Cursor --- .../__tests__/fetchComponentSource.spec.ts | 23 +++++++++++++++++++ .../frontComponentCacheStorageService.ts | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts b/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts index ff3ca7e9c2..66ca456345 100644 --- a/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts +++ b/packages/twenty-front-component-renderer/src/remote/worker/utils/__tests__/fetchComponentSource.spec.ts @@ -161,6 +161,29 @@ describe('fetchComponentSource', () => { expect(fetchMock).toHaveBeenCalledTimes(1); }); + it('falls back to the network when the cached response body is unreadable', async () => { + const cache = new FakeCache(); + + setupCaches(cache); + + cache.match.mockResolvedValueOnce({ + text: async () => { + throw new Error('body unreadable'); + }, + }); + + const fetchMock = jest.fn(async () => + createFakeJsResponse(COMPONENT_SOURCE), + ); + + globalThis.fetch = fetchMock as unknown as typeof fetch; + + const source = await fetchComponentSource({ url: FINGERPRINTED_URL }); + + expect(source).toBe(COMPONENT_SOURCE); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + it('does not cache a network response whose checksum does not match the URL', async () => { const cache = new FakeCache(); diff --git a/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts b/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts index 8884cfb06d..242e976557 100644 --- a/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts +++ b/packages/twenty-front-component-renderer/src/remote/worker/utils/frontComponentCacheStorageService.ts @@ -48,7 +48,9 @@ export const frontComponentCacheStorageService = { try { const cachedResponse = await cache.match(url); - return isDefined(cachedResponse) ? cachedResponse.text() : undefined; + return isDefined(cachedResponse) + ? await cachedResponse.text() + : undefined; } catch { return undefined; }