Browse Source

fix(frontend): remove unused MSW browser worker (#6859)

The frontend uses MSW only through its Node test interceptor, but the unused browser worker requires synchronization after dependency updates and currently breaks make verify.

Remove the unused asset, worker-directory configuration, and freshness checks while preserving Node MSW tests and the separate PWA worker. Update tooling descriptions to match.

Fixes #6858
Anthony Ilinykh 6 hours ago
parent
commit
a1f2589eb5

+ 2 - 2
.gitattributes

@@ -1,3 +1,3 @@
-# LF in every checkout, Windows included: format-check, the msw worker check and
-# tests that parse repo files compare bytes, so a CRLF working copy fails them.
+# LF in every checkout, Windows included: format-check and tests that parse repo
+# files compare bytes, so a CRLF working copy fails them.
 * text=auto eol=lf

+ 1 - 1
.github/claude/issue-analyst-context.md

@@ -152,7 +152,7 @@ It is paths-filtered, so a docs-only or workflow-only change produces no run.
 | `golangci` | `golangci-lint` |
 | `fuzz-smoke` | 30s each on `FuzzParseLink` and `FuzzDecodeCertPin` |
 | `codegen` | `npm run gen` then `git diff --exit-code` on the generated files |
-| `frontend` | MSW worker drift, lint, format:check, typecheck, `npm test` (Vitest + headless-Chromium Storybook), build, build-storybook, `npm audit` |
+| `frontend` | lint, format:check, typecheck, `npm test` (Vitest + headless-Chromium Storybook), build, build-storybook, `npm audit` |
 
 **What CI does NOT prove.** These test families `t.Skip` unless an environment
 variable is set, and CI sets only the PostgreSQL ones above:

+ 0 - 3
.github/workflows/ci.yml

@@ -201,9 +201,6 @@ jobs:
       - name: Install
         run: npm ci
         working-directory: frontend
-      - name: Verify generated MSW worker is current
-        run: git diff --exit-code -- public/mockServiceWorker.js package-lock.json
-        working-directory: frontend
       - name: Lint
         run: npm run lint
         working-directory: frontend

+ 1 - 5
Makefile

@@ -45,10 +45,6 @@ format-check: ## oxfmt in check mode on frontend sources
 typecheck: ## tsc --noEmit
 	cd $(FRONTEND) && npm run typecheck
 
-.PHONY: msw-worker-check
-msw-worker-check: ## Verify the tracked worker matches the installed MSW runtime
-	cmp $(FRONTEND)/public/mockServiceWorker.js $(FRONTEND)/node_modules/msw/lib/mockServiceWorker.js
-
 .PHONY: test-go
 test-go: dist-stub ## Go tests (shuffle, no cache)
 	go test -shuffle=on -count=1 $(GO_PKGS)
@@ -91,5 +87,5 @@ build-storybook: ## Build the static Storybook (compile-checks all stories)
 # The PR gate. Matches ci.yml: codegen freshness, both linters, the formatter,
 # typecheck, both test suites, a full build, and the Storybook compile-check.
 .PHONY: verify
-verify: gen-check lint format-check typecheck msw-worker-check test build build-storybook ## Full local gate (mirrors CI)
+verify: gen-check lint format-check typecheck test build build-storybook ## Full local gate (mirrors CI)
 	@echo "verify: OK"

+ 0 - 5
frontend/package.json

@@ -115,10 +115,5 @@
     "tree-sitter-json": false,
     "msw": false,
     "esbuild": false
-  },
-  "msw": {
-    "workerDirectory": [
-      "public"
-    ]
   }
 }

+ 0 - 361
frontend/public/mockServiceWorker.js

@@ -1,361 +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.15.0'
-const INTEGRITY_CHECKSUM = '03cb67ac84128e63d7cd722a6e5b7f1e'
-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 '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) {
-  const requestInterceptedAt = Date.now()
-
-  // 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 terminated (still remains active until the next reload).
-  if (activeClientIds.size === 0) {
-    return
-  }
-
-  const requestId = crypto.randomUUID()
-  event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
-})
-
-/**
- * @param {FetchEvent} event
- * @param {string} requestId
- * @param {number} requestInterceptedAt
- */
-async function handleRequest(event, requestId, requestInterceptedAt) {
-  const client = await resolveMainClient(event)
-  const requestCloneForEvents = event.request.clone()
-  const response = await getResponse(
-    event,
-    client,
-    requestId,
-    requestInterceptedAt,
-  )
-
-  // 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)
-
-    // Omit the body of server-sent event stream responses.
-    // Cloning such responses would prevent client-side stream cancelations
-    // from reaching the original stream (a teed stream only cancels its
-    // source once both of its branches cancel) and would buffer the
-    // entire stream into the unconsumed clone indefinitely.
-    const isEventStreamResponse = response.headers
-      .get('content-type')
-      ?.toLowerCase()
-      .startsWith('text/event-stream')
-
-    // Clone the response so both the client and the library could consume it.
-    const responseClone = isEventStreamResponse ? null : response.clone()
-
-    sendToClient(
-      client,
-      {
-        type: 'RESPONSE',
-        payload: {
-          isMockedResponse: IS_MOCKED_RESPONSE in response,
-          request: {
-            id: requestId,
-            ...serializedRequest,
-          },
-          response: {
-            type: response.type,
-            status: response.status,
-            statusText: response.statusText,
-            headers: Object.fromEntries(response.headers.entries()),
-            body: responseClone ? responseClone.body : null,
-          },
-        },
-      },
-      responseClone && 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<Client | undefined>}
- */
-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
- * @param {number} requestInterceptedAt
- * @returns {Promise<Response>}
- */
-async function getResponse(event, client, requestId, requestInterceptedAt) {
-  // 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,
-        interceptedAt: requestInterceptedAt,
-        ...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<Transferable>} transferrables
- * @returns {Promise<any>}
- */
-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,
-  }
-}