Skip to content

Commit

Permalink
feat(remote-server): implement handling for socket requests
Browse files Browse the repository at this point in the history
This commit implements the bulk of the handling for the remote server, particularly the passthrough handling for the internal requests.
  • Loading branch information
chrisb2244 committed Feb 15, 2024
1 parent 0684682 commit 3ea224f
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 9 deletions.
14 changes: 13 additions & 1 deletion src/core/handlers/RemoteRequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,19 @@ export class RemoteRequestHandler extends HttpHandler {
responsePromise.resolve(response)
})

return await responsePromise
const response = await responsePromise
if (typeof response === 'undefined') {
const dummyResponse = new Response(
JSON.stringify({
message: 'Unhandled request',
sender: 'MSW_RemoteRequestHandler',
}),
)
dummyResponse.headers.set('x-msw-unhandled-remote-handler', 'true')
return dummyResponse
}

return response
})
}
}
31 changes: 27 additions & 4 deletions src/core/handlers/RequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,33 @@ export abstract class RequestHandler<
request: args.request,
parsedResult,
})
const mockedResponse = (await executeResolver({
...resolverExtras,
request: args.request,
})) as Response

const mockedResponsePromise = (
executeResolver({
...resolverExtras,
requestId: args.requestId,
request: args.request,
}) as Promise<Response>
).catch((errorOrResponse) => {
// Allow throwing a Response instance in a response resolver.
if (errorOrResponse instanceof Response) {
return errorOrResponse
}

// Otherwise, throw the error as-is.
throw errorOrResponse
})

const mockedResponse = await mockedResponsePromise
if (
mockedResponse &&
mockedResponse.headers.get('x-msw-unhandled-remote-handler')
) {
// If the response is marked as unhandled by a remote handler,
// return null to indicate it was not handled
// (despite passing through the RemoteRequestHandler, which matched).
return null
}

const executionResult = this.createExecutionResult({
// Pass the cloned request to the result so that logging
Expand Down
57 changes: 55 additions & 2 deletions src/node/SetupServerCommonApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,28 @@ import { handleRequest } from '~/core/utils/handleRequest'
import type { RequestHandler } from '~/core/handlers/RequestHandler'
import { mergeRight } from '~/core/utils/internal/mergeRight'
import { devUtils } from '~/core/utils/internal/devUtils'
import type { SetupServerCommon } from './glossary'
import type { ListenOptions, SetupServerCommon } from './glossary'
import type { Socket } from 'socket.io-client'
import {
SyncServerEventsMap,
createSyncClient,
createWebSocketServerUrl,
} from './setupRemoteServer'
import {
onAnyEvent,
serializeEventPayload,
} from '~/core/utils/internal/emitterUtils'
import { RemoteRequestHandler } from '~/core/handlers/RemoteRequestHandler'
import { http, passthrough } from '~/core'

export const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = {
onUnhandledRequest: 'warn',
}

interface InteractiveRequest extends Request {
respondWith(response: Response): void
}

export class SetupServerCommonApi
extends SetupApi<LifeCycleEventsMap>
implements SetupServerCommon
Expand All @@ -30,7 +46,8 @@ export class SetupServerCommonApi
Array<Interceptor<HttpRequestEventMap>>,
HttpRequestEventMap
>
private resolvedOptions: RequiredDeep<SharedOptions>
private resolvedOptions: RequiredDeep<ListenOptions>
private socket: Socket<SyncServerEventsMap> | undefined

constructor(
interceptors: Array<{ new (): Interceptor<HttpRequestEventMap> }>,
Expand All @@ -53,6 +70,42 @@ export class SetupServerCommonApi
*/
private init(): void {
this.interceptor.on('request', async ({ request, requestId }) => {
// If in remote mode, await the WebSocket connection promise
// before handling any requests.
if (this.resolvedOptions.remotePort) {
// Bypass handling if the request is for the socket.io connection
const remoteResolutionUrl = createWebSocketServerUrl(
this.resolvedOptions.remotePort,
)
// Build a pattern to match the socket.io connection
remoteResolutionUrl.pathname = '/socket.io'
const matcher = new RegExp(`${remoteResolutionUrl.href}/.*`)
const isSocketRequest = matcher.test(request.url)
if (isSocketRequest) {
return
}

if (typeof this.socket === 'undefined') {
this.socket = await createSyncClient(this.resolvedOptions.remotePort)
}

if (typeof this.socket !== 'undefined') {
const initialHandlers = this.handlersController.currentHandlers()
this.handlersController.prepend([
http.all(remoteResolutionUrl.href, passthrough),
new RemoteRequestHandler({
requestId,
socket: this.socket,
}),
])
this.socket.on('disconnect', () => {
console.log('Remote handler disconnected')
this.handlersController.reset(initialHandlers)
this.socket = undefined
})
}
}

const response = await handleRequest(
request,
requestId,
Expand Down
12 changes: 10 additions & 2 deletions src/node/setupRemoteServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,27 @@ export class SetupRemoteServerApi
.on('SIGINT', () => closeSyncServer(server))

server.on('connection', (socket) => {
console.log('WS server connection!')

socket.on('request', async ({ requestId, serializedRequest }) => {
const request = deserializeRequest(serializedRequest)
const response = await handleRequest(
request,
requestId,
this.currentHandlers,
this.handlersController.currentHandlers(),
/**
* @todo Support resolve options from the `.listen()` call.
*/
{ onUnhandledRequest() {} },
placeholderEmitter,
)

console.log(
`[msw] RemoteServerHandling: ${response ? 'Handled' : 'Ignored'} `,
request.method,
request.url,
)

socket.emit(
'response',
response ? await serializeResponse(response) : undefined,
Expand Down Expand Up @@ -193,7 +201,7 @@ async function closeSyncServer(server: WebSocketServer): Promise<void> {
return serverClosePromise
}

function createWebSocketServerUrl(port: number): URL {
export function createWebSocketServerUrl(port: number): URL {
const url = new URL('http://localhost')
url.port = port.toString()
return url
Expand Down

0 comments on commit 3ea224f

Please sign in to comment.