From 1f2bf4928113cec587a02f77b7aa786bee9dbc7b Mon Sep 17 00:00:00 2001 From: Allan Lasser Date: Mon, 2 Dec 2024 17:10:19 -0500 Subject: [PATCH] Try calling searchWithin from inside client --- public/mockServiceWorker.js | 194 +++++++++--------- src/langs/json/en.json | 1 + src/lib/components/viewer/Search.svelte | 75 ++++--- .../components/viewer/ViewerContext.svelte | 6 - .../viewer/stories/Search.stories.svelte | 61 ++++-- .../(app)/documents/[id]-[slug]/+page.svelte | 3 +- .../(app)/documents/[id]-[slug]/+page.ts | 3 +- src/test/handlers/documents.ts | 7 + 8 files changed, 199 insertions(+), 151 deletions(-) diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index fead0b3ff..1f45c4cf8 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -2,15 +2,13 @@ /* tslint:disable */ /** - * Mock Service Worker. + * Mock Service Worker (1.3.5). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.6.6' -const INTEGRITY_CHECKSUM = 'ca7800994cc8bfb5eb961e037c877074' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' const activeClientIds = new Set() self.addEventListener('install', function () { @@ -49,10 +47,7 @@ self.addEventListener('message', async function (event) { case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, + payload: INTEGRITY_CHECKSUM, }) break } @@ -62,12 +57,7 @@ self.addEventListener('message', async function (event) { sendToClient(client, { type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, + payload: true, }) break } @@ -96,6 +86,12 @@ self.addEventListener('message', async function (event) { self.addEventListener('fetch', function (event) { const { request } = event + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } // Bypass navigation requests. if (request.mode === 'navigate') { @@ -116,8 +112,29 @@ self.addEventListener('fetch', function (event) { } // Generate unique request ID. - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId)) + const requestId = Math.random().toString(16).slice(2) + + event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) }) async function handleRequest(event, requestId) { @@ -129,24 +146,21 @@ async function handleRequest(event, requestId) { // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { ;(async function () { - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - requestId, - isMockedResponse: IS_MOCKED_RESPONSE in response, - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - body: responseClone.body, - headers: Object.fromEntries(responseClone.headers.entries()), - }, + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, }, - [responseClone.body], - ) + }) })() } @@ -160,10 +174,6 @@ async function handleRequest(event, requestId) { 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 } @@ -186,22 +196,20 @@ async function resolveMainClient(event) { async function getResponse(event, client, requestId) { const { request } = event - - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = request.clone() + const clonedRequest = 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) + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()) - // 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. - headers.delete('accept', 'msw/passthrough') + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass'] - return fetch(requestClone, { headers }) + return fetch(clonedRequest, { headers }) } // Bypass mocking when the client is not active. @@ -217,46 +225,57 @@ async function getResponse(event, client, requestId) { return passthrough() } + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough() + } + // Notify the client that a request has been intercepted. - const requestBuffer = await request.arrayBuffer() - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - 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: requestBuffer, - keepalive: request.keepalive, - }, + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, }, - [requestBuffer], - ) + }) switch (clientMessage.type) { case 'MOCK_RESPONSE': { return respondWithMock(clientMessage.data) } - case 'PASSTHROUGH': { + case 'MOCK_NOT_FOUND': { return passthrough() } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data + const networkError = new Error(message) + networkError.name = name + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError + } } return passthrough() } -function sendToClient(client, message, transferrables = []) { +function sendToClient(client, message) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -268,28 +287,17 @@ function sendToClient(client, message, transferrables = []) { resolve(event.data) } - client.postMessage( - message, - [channel.port2].concat(transferrables.filter(Boolean)), - ) + client.postMessage(message, [channel.port2]) }) } -async 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, +function sleep(timeMs) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs) }) +} - return mockedResponse +async function respondWithMock(response) { + await sleep(response.delay) + return new Response(response.body, response) } diff --git a/src/langs/json/en.json b/src/langs/json/en.json index 9fb065c5b..bccad5605 100644 --- a/src/langs/json/en.json +++ b/src/langs/json/en.json @@ -11,6 +11,7 @@ "title": "Title" }, "search": { + "loading": "Searching…", "matchingResults": "{n, plural, one {# matching result} other {# matching results}}", "reset": "Clear Search", "help": "Use filters like user:, project: or organization: to refine searches. Use sort: to order results.", diff --git a/src/lib/components/viewer/Search.svelte b/src/lib/components/viewer/Search.svelte index 53ae1ada1..2b27bd3a4 100644 --- a/src/lib/components/viewer/Search.svelte +++ b/src/lib/components/viewer/Search.svelte @@ -3,58 +3,65 @@ Assumes it's a child of a ViewerContext -->
- {#each resultsPages as [pageNumber, resultsList]} - - - - {:else} - -

{$_("search.empty")}

+ {#await search} + + {$_("search.loading")} - {/each} + {:then resultsPages} + {#each resultsPages as [pageNumber, resultsList]} + + + + {:else} + + {$_("search.empty")} + + {/each} + {:catch err} + {err.message} + {/await}