mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-29 06:31:58 +00:00
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
This commit is contained in:
1 parent
608ef4e401
commit
a1f2589eb5
6 files changed
+4
-377
No files matched your search
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
# LF in every checkout, Windows included: format-check, the msw worker check and
|
# LF in every checkout, Windows included: format-check and tests that parse repo
|
||||||
# tests that parse repo files compare bytes, so a CRLF working copy fails them.
|
# files compare bytes, so a CRLF working copy fails them.
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
@@ -152,7 +152,7 @@ It is paths-filtered, so a docs-only or workflow-only change produces no run.
|
|||||||
| `golangci` | `golangci-lint` |
|
| `golangci` | `golangci-lint` |
|
||||||
| `fuzz-smoke` | 30s each on `FuzzParseLink` and `FuzzDecodeCertPin` |
|
| `fuzz-smoke` | 30s each on `FuzzParseLink` and `FuzzDecodeCertPin` |
|
||||||
| `codegen` | `npm run gen` then `git diff --exit-code` on the generated files |
|
| `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
|
**What CI does NOT prove.** These test families `t.Skip` unless an environment
|
||||||
variable is set, and CI sets only the PostgreSQL ones above:
|
variable is set, and CI sets only the PostgreSQL ones above:
|
||||||
|
|||||||
@@ -201,9 +201,6 @@ jobs:
|
|||||||
- name: Install
|
- name: Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
working-directory: frontend
|
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
|
- name: Lint
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ format-check: ## oxfmt in check mode on frontend sources
|
|||||||
typecheck: ## tsc --noEmit
|
typecheck: ## tsc --noEmit
|
||||||
cd $(FRONTEND) && npm run typecheck
|
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
|
.PHONY: test-go
|
||||||
test-go: dist-stub ## Go tests (shuffle, no cache)
|
test-go: dist-stub ## Go tests (shuffle, no cache)
|
||||||
go test -shuffle=on -count=1 $(GO_PKGS)
|
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,
|
# The PR gate. Matches ci.yml: codegen freshness, both linters, the formatter,
|
||||||
# typecheck, both test suites, a full build, and the Storybook compile-check.
|
# typecheck, both test suites, a full build, and the Storybook compile-check.
|
||||||
.PHONY: verify
|
.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"
|
@echo "verify: OK"
|
||||||
@@ -115,10 +115,5 @@
|
|||||||
"tree-sitter-json": false,
|
"tree-sitter-json": false,
|
||||||
"msw": false,
|
"msw": false,
|
||||||
"esbuild": false
|
"esbuild": false
|
||||||
},
|
|
||||||
"msw": {
|
|
||||||
"workerDirectory": [
|
|
||||||
"public"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user