From 59c7d97aade79e1ae8696d19f0821b72a3a99570 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:42:46 +0200 Subject: [PATCH 1/2] [chore] Revert MSW usage on simple example --- examples/simple/package.json | 7 +- examples/simple/public/mockServiceWorker.js | 344 -------------------- examples/simple/src/dataProvider.tsx | 5 +- examples/simple/src/fakeServer.ts | 13 - examples/simple/src/index.tsx | 91 +++--- yarn.lock | 4 +- 6 files changed, 47 insertions(+), 417 deletions(-) delete mode 100644 examples/simple/public/mockServiceWorker.js delete mode 100644 examples/simple/src/fakeServer.ts diff --git a/examples/simple/package.json b/examples/simple/package.json index 0c85407e9fa..0a8303a36c2 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -17,7 +17,7 @@ "fakerest": "^4.1.3", "jsonexport": "^3.2.0", "lodash": "~4.17.5", - "ra-data-simple-rest": "^5.10.1", + "ra-data-fakerest": "^5.10.1", "ra-i18n-polyglot": "^5.10.1", "ra-input-rich-text": "^5.10.1", "ra-language-english": "^5.10.1", @@ -36,10 +36,5 @@ "react-simple-animate": "^3.5.3", "typescript": "^5.1.3", "vite": "^6.2.6" - }, - "msw": { - "workerDirectory": [ - "public" - ] } } diff --git a/examples/simple/public/mockServiceWorker.js b/examples/simple/public/mockServiceWorker.js deleted file mode 100644 index be4527c7ee5..00000000000 --- a/examples/simple/public/mockServiceWorker.js +++ /dev/null @@ -1,344 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - */ - -const PACKAGE_VERSION = '2.10.4' -const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() - -addEventListener('install', function () { - self.skipWaiting() -}) - -addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -addEventListener('message', async function (event) { - const clientId = Reflect.get(event.source || {}, 'id') - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, - }) - break - } - - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -addEventListener('fetch', function (event) { - // Bypass navigation requests. - if (event.request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if ( - event.request.cache === 'only-if-cached' && - event.request.mode !== 'same-origin' - ) { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been deleted (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId)) -}) - -/** - * @param {FetchEvent} event - * @param {string} requestId - */ -async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const requestCloneForEvents = event.request.clone() - const response = await getResponse(event, client, requestId) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents) - - // Clone the response so both the client and the library could consume it. - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - isMockedResponse: IS_MOCKED_RESPONSE in response, - request: { - id: requestId, - ...serializedRequest, - }, - response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, - }, - }, - }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ) - } - - return response -} - -/** - * Resolve the main client for the given event. - * Client that issues a request doesn't necessarily equal the client - * that registered the worker. It's with the latter the worker should - * communicate with during the response resolving phase. - * @param {FetchEvent} event - * @returns {Promise} - */ -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (activeClientIds.has(event.clientId)) { - return client - } - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -/** - * @param {FetchEvent} event - * @param {Client | undefined} client - * @param {string} requestId - * @returns {Promise} - */ -async function getResponse(event, client, requestId) { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone() - - function passthrough() { - // Cast the request headers to a new Headers instance - // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers) - - // Remove the "accept" header value that marked this request as passthrough. - // This prevents request alteration and also keeps it compliant with the - // user-defined CORS policies. - const acceptHeader = headers.get('accept') - if (acceptHeader) { - const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) - - if (filteredValues.length > 0) { - headers.set('accept', filteredValues.join(', ')) - } else { - headers.delete('accept') - } - } - - return fetch(requestClone, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request) - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - ...serializedRequest, - }, - }, - [serializedRequest.body], - ) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'PASSTHROUGH': { - return passthrough() - } - } - - return passthrough() -} - -/** - * @param {Client} client - * @param {any} message - * @param {Array} transferrables - * @returns {Promise} - */ -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [ - channel.port2, - ...transferrables.filter(Boolean), - ]) - }) -} - -/** - * @param {Response} response - * @returns {Response} - */ -function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error() - } - - const mockedResponse = new Response(response.body, response) - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }) - - return mockedResponse -} - -/** - * @param {Request} request - */ -async function serializeRequest(request) { - return { - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.arrayBuffer(), - keepalive: request.keepalive, - } -} diff --git a/examples/simple/src/dataProvider.tsx b/examples/simple/src/dataProvider.tsx index ba0adc8e8a3..7c18a80f876 100644 --- a/examples/simple/src/dataProvider.tsx +++ b/examples/simple/src/dataProvider.tsx @@ -1,10 +1,11 @@ -import simpleRestProvider from 'ra-data-simple-rest'; +import simpleRestProvider from 'ra-data-fakerest'; import { DataProvider, withLifecycleCallbacks, HttpError } from 'react-admin'; import get from 'lodash/get'; import addUploadFeature from './addUploadFeature'; import { queryClient } from './queryClient'; +import data from './data'; -const defaultDataProvider = simpleRestProvider('http://localhost:4000'); +const defaultDataProvider = simpleRestProvider(data); const dataProvider = withLifecycleCallbacks(defaultDataProvider, [ { diff --git a/examples/simple/src/fakeServer.ts b/examples/simple/src/fakeServer.ts deleted file mode 100644 index 14bfa38dc03..00000000000 --- a/examples/simple/src/fakeServer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import data from './data'; -import { http } from 'msw'; -import { setupWorker } from 'msw/browser'; -import { getMswHandler, withDelay } from 'fakerest'; - -const handler = getMswHandler({ - baseUrl: 'http://localhost:4000', - data, - middlewares: [withDelay(300)], -}); -export const worker = setupWorker(http.all(/http:\/\/localhost:4000/, handler)); - -export default () => worker; diff --git a/examples/simple/src/index.tsx b/examples/simple/src/index.tsx index 723832124bf..e83d410c954 100644 --- a/examples/simple/src/index.tsx +++ b/examples/simple/src/index.tsx @@ -15,57 +15,48 @@ import posts from './posts'; import users from './users'; import tags from './tags'; import { queryClient } from './queryClient'; -import { worker } from './fakeServer'; const container = document.getElementById('root') as HTMLElement; const root = createRoot(container); -worker.start({ onUnhandledRequest: 'bypass', quiet: true }).then(() => { - root.render( - - - - - - - - - } - /> - - } - /> - - - - } - /> - - - - } - /> - - - - ); -}); +root.render( + + + + + + + + } + /> + + } + /> + + + } + /> + + + } + /> + + + +); diff --git a/yarn.lock b/yarn.lock index aa7b78e1f4d..c4e19f46ded 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16337,7 +16337,7 @@ __metadata: languageName: unknown linkType: soft -"ra-data-simple-rest@npm:^5.0.0, ra-data-simple-rest@npm:^5.10.1, ra-data-simple-rest@npm:^5.3.0, ra-data-simple-rest@workspace:packages/ra-data-simple-rest": +"ra-data-simple-rest@npm:^5.0.0, ra-data-simple-rest@npm:^5.3.0, ra-data-simple-rest@workspace:packages/ra-data-simple-rest": version: 0.0.0-use.local resolution: "ra-data-simple-rest@workspace:packages/ra-data-simple-rest" dependencies: @@ -18047,7 +18047,7 @@ __metadata: jsonexport: "npm:^3.2.0" little-state-machine: "npm:^4.8.1" lodash: "npm:~4.17.5" - ra-data-simple-rest: "npm:^5.10.1" + ra-data-fakerest: "npm:^5.10.1" ra-i18n-polyglot: "npm:^5.10.1" ra-input-rich-text: "npm:^5.10.1" ra-language-english: "npm:^5.10.1" From 2ecba858f411b50b2278e0efef29d914213ba046 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:27:56 +0200 Subject: [PATCH 2/2] Fix dataProvider delay --- examples/simple/src/dataProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple/src/dataProvider.tsx b/examples/simple/src/dataProvider.tsx index 7c18a80f876..1d5c913f408 100644 --- a/examples/simple/src/dataProvider.tsx +++ b/examples/simple/src/dataProvider.tsx @@ -1,11 +1,11 @@ -import simpleRestProvider from 'ra-data-fakerest'; +import fakeRestProvider from 'ra-data-fakerest'; import { DataProvider, withLifecycleCallbacks, HttpError } from 'react-admin'; import get from 'lodash/get'; import addUploadFeature from './addUploadFeature'; import { queryClient } from './queryClient'; import data from './data'; -const defaultDataProvider = simpleRestProvider(data); +const defaultDataProvider = fakeRestProvider(data, true, 300); const dataProvider = withLifecycleCallbacks(defaultDataProvider, [ {