Skip to content

Commit 055ad91

Browse files
committed
fix: support legacy onUnhandledRequest
1 parent 18faa73 commit 055ad91

File tree

8 files changed

+146
-97
lines changed

8 files changed

+146
-97
lines changed

src/browser/setup-worker.ts

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type NetworkApi,
77
} from '~/core/new/define-network'
88
import {
9-
onUnhandledFrame,
9+
fromLegacyOnUnhandledRequest,
1010
UnhandledFrameStrategy,
1111
} from '~/core/new/on-unhandled-frame'
1212
import { UnhandledRequestCallback } from '~/core/utils/request/onUnhandledRequest'
@@ -76,44 +76,27 @@ export function setupWorker(
7676
}),
7777
],
7878
handlers,
79-
async onUnhandledFrame({ frame, defaults }) {
80-
/**
81-
* @note `setupWorker` does not handle unhandled WebSocket events.
82-
* This will be a new API in 3.0, most likely. We can remove this
83-
* mapping then.
84-
*/
85-
if (
86-
frame.protocol === 'http' &&
87-
options?.onUnhandledRequest !== undefined
88-
) {
89-
if (typeof options.onUnhandledRequest === 'function') {
90-
options.onUnhandledRequest(frame.data.request, {
91-
warning: defaults.warn,
92-
error: defaults.error,
93-
})
94-
} else {
95-
await onUnhandledFrame(frame, 'warn')
96-
}
97-
}
98-
},
79+
onUnhandledFrame: fromLegacyOnUnhandledRequest(
80+
() => options?.onUnhandledRequest,
81+
),
9982
})
10083

10184
await network.enable()
10285
},
103-
listHandlers() {
104-
return network.listHandlers()
86+
stop() {
87+
network.disable()
10588
},
106-
use(...handlers) {
107-
return network.use(...handlers)
89+
get listHandlers() {
90+
return network.listHandlers.bind(network)
10891
},
109-
resetHandlers(...handlers) {
110-
return network.resetHandlers(...handlers)
92+
get use() {
93+
return network.use.bind(network)
11194
},
112-
restoreHandlers() {
113-
return network.restoreHandlers()
95+
get resetHandlers() {
96+
return network.resetHandlers.bind(network)
11497
},
115-
stop() {
116-
network.disable()
98+
get restoreHandlers() {
99+
return network.restoreHandlers.bind(network)
117100
},
118101
}
119102
}

src/core/new/define-network.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface DefineNetworkOptions<Handler extends AnyHandler> {
2323
/**
2424
* A list of the initial handlers.
2525
*/
26-
handlers?: Array<Handler>
26+
handlers: Array<Handler>
2727

2828
/**
2929
* A custom handlers controller to use.
@@ -69,8 +69,7 @@ export function defineNetwork<Handler extends AnyHandler>(
6969
const events = new Emitter()
7070

7171
const handlersController =
72-
options.handlersController ||
73-
inMemoryHandlersController(options.handlers || [])
72+
options.handlersController || inMemoryHandlersController(options.handlers)
7473

7574
return {
7675
async enable() {
@@ -85,7 +84,18 @@ export function defineNetwork<Handler extends AnyHandler>(
8584
)
8685

8786
if (!wasFrameHandled) {
88-
await onUnhandledFrame(frame, options.onUnhandledFrame || 'bypass')
87+
await onUnhandledFrame(
88+
frame,
89+
options.onUnhandledFrame || 'bypass',
90+
).catch((error) => {
91+
/**
92+
* @fixme `.errorWith()` should exist on WebSocket frames too
93+
* since you can error the connection by dispatching the "error" event.
94+
*/
95+
if (frame.protocol === 'http') {
96+
frame.errorWith(error)
97+
}
98+
})
8999
}
90100
})
91101

src/core/new/on-unhandled-frame.ts

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { isCommonAssetRequest } from '../isCommonAssetRequest'
22
import { devUtils, InternalError } from '../utils/internal/devUtils'
3+
import { UnhandledRequestStrategy } from '../utils/request/onUnhandledRequest'
34
import type { NetworkFrame } from './sources/index'
45

56
export type UnhandledFrameStrategy = 'bypass' | 'warn' | 'error'
@@ -12,7 +13,7 @@ export type UnhandledFrameDefaults = {
1213
export type UnhandledFrameCallback = (args: {
1314
frame: NetworkFrame
1415
defaults: UnhandledFrameDefaults
15-
}) => void
16+
}) => Promise<void> | void
1617

1718
export async function onUnhandledFrame(
1819
frame: NetworkFrame,
@@ -25,31 +26,43 @@ export async function onUnhandledFrame(
2526

2627
const message = await frame.getUnhandledFrameMessage()
2728

28-
if (strategy === 'warn') {
29-
return devUtils.warn('Warning: %s', message)
30-
}
29+
switch (strategy) {
30+
case 'warn': {
31+
return devUtils.warn('Warning: %s', message)
32+
}
3133

32-
if (strategy === 'error') {
33-
return devUtils.error('Error: %s', message)
34-
}
34+
case 'error': {
35+
// Print a developer-friendly error.
36+
devUtils.error('Error: %s', message)
37+
38+
return Promise.reject(
39+
new Error(
40+
devUtils.formatMessage(
41+
'Cannot bypass a request when using the "error" strategy for the "onUnhandledRequest" option.',
42+
),
43+
),
44+
)
45+
}
3546

36-
throw new InternalError(
37-
devUtils.formatMessage(
38-
'Failed to react to an unhandled network frame: unknown strategy "%s". Please provide one of the supported strategies ("bypass", "warn", "error") or a custom callback function as the value of the "onUnhandledRequest" option.',
39-
strategy,
40-
),
41-
)
47+
default: {
48+
throw new InternalError(
49+
devUtils.formatMessage(
50+
'Failed to react to an unhandled network frame: unknown strategy "%s". Please provide one of the supported strategies ("bypass", "warn", "error") or a custom callback function as the value of the "onUnhandledRequest" option.',
51+
strategy satisfies never,
52+
),
53+
)
54+
}
55+
}
4256
}
4357

4458
if (typeof strategyOrCallback === 'function') {
45-
strategyOrCallback({
59+
return strategyOrCallback({
4660
frame,
4761
defaults: {
4862
warn: applyStrategy.bind(null, 'warn'),
4963
error: applyStrategy.bind(null, 'warn'),
5064
},
5165
})
52-
return
5366
}
5467

5568
// Ignore static assets, framework/bundler requests, modules served via HTTP.
@@ -59,3 +72,28 @@ export async function onUnhandledFrame(
5972

6073
await applyStrategy(strategyOrCallback)
6174
}
75+
76+
export function fromLegacyOnUnhandledRequest(
77+
getLegacyValue: () => UnhandledRequestStrategy | undefined,
78+
): UnhandledFrameCallback {
79+
return ({ frame, defaults }) => {
80+
if (frame.protocol !== 'http') {
81+
return
82+
}
83+
84+
const legacyOnUnhandledRequestStrategy = getLegacyValue()
85+
86+
if (legacyOnUnhandledRequestStrategy === undefined) {
87+
return
88+
}
89+
90+
if (typeof legacyOnUnhandledRequestStrategy === 'function') {
91+
return legacyOnUnhandledRequestStrategy(frame.data.request, {
92+
warning: defaults.warn,
93+
error: defaults.error,
94+
})
95+
}
96+
97+
return onUnhandledFrame(frame, legacyOnUnhandledRequestStrategy)
98+
}
99+
}

src/core/new/sources/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export abstract class NetworkSource {
1414
* @param sources A list of network sources.
1515
*/
1616
static from(...sources: Array<NetworkSource>): NetworkSource {
17-
return new BatchNetworkSource(sources)
17+
return sources.length > 1 ? new BatchNetworkSource(sources) : sources[0]
1818
}
1919

2020
protected emitter: Emitter<NetworkSourceEventMap>

src/core/new/sources/interceptor-source.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,11 @@ class InterceptorHttpNetworkFrame extends HttpNetworkFrame {
143143
return this.respondWith(reason)
144144
}
145145

146-
/**
147-
* @note We do not currently support the `.errorWith()` flow.
148-
* Instead, exceptions are expected to throw as-is to be coerced into 500 responses.
149-
*/
150-
throw reason
146+
if (reason instanceof InternalError) {
147+
throw reason
148+
}
149+
150+
this.#controller.errorWith(reason as any)
151151
}
152152

153153
public passthrough(): void {

src/node/setup-server.ts

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,82 @@ import {
99
ReversibleProxy,
1010
reversibleProxy,
1111
} from '~/core/utils/internal/reversibleProxy'
12-
import { SetupServerCommonApi } from './setup-server-common'
1312
import {
1413
type AsyncHandlersController,
1514
asyncHandlersController,
1615
} from './async-handler-controller'
17-
import type { ListenOptions, SetupServer } from './glossary'
16+
import type { ListenOptions, SetupServer, SetupServerCommon } from './glossary'
1817
import { HandlersController } from '~/core/SetupApi'
18+
import { defineNetwork, NetworkApi } from '~/core/new/define-network'
19+
import { InterceptorSource } from '~/core/new/sources/interceptor-source'
20+
import { fromLegacyOnUnhandledRequest } from '~/core/new/on-unhandled-frame'
1921

2022
/**
2123
* Enables request interception in Node.js with the given request handlers.
2224
*
2325
* @see {@link https://mswjs.io/docs/api/setup-server `setupServer()` API reference}
2426
*/
2527
export function setupServer(...handlers: Array<AnyHandler>): SetupServerApi {
26-
return new SetupServerApi(handlers)
28+
return new SetupServerApi(handlers, [
29+
new ClientRequestInterceptor(),
30+
new XMLHttpRequestInterceptor(),
31+
new FetchInterceptor(),
32+
new WebSocketInterceptor() as any,
33+
])
2734
}
2835

29-
export class SetupServerApi
30-
extends SetupServerCommonApi
31-
implements SetupServer
32-
{
33-
#currentHandlersProxy?: ReversibleProxy<HandlersController['currentHandlers']>
36+
export class SetupServerApi implements SetupServer {
37+
public events: SetupServerCommon['events']
38+
public use: SetupServerCommon['use']
39+
public resetHandlers: SetupServerCommon['resetHandlers']
40+
public restoreHandlers: SetupServerCommon['restoreHandlers']
41+
public listHandlers: SetupServerCommon['listHandlers']
3442

35-
protected handlersController: AsyncHandlersController<AnyHandler>
43+
#network: NetworkApi<AnyHandler>
44+
#handlersController: AsyncHandlersController<AnyHandler>
45+
#currentHandlersProxy?: ReversibleProxy<HandlersController['currentHandlers']>
46+
#listenOptions?: Partial<ListenOptions>
3647

3748
constructor(
3849
handlers: Array<AnyHandler>,
39-
interceptors: Array<Interceptor<any>> = [
40-
new ClientRequestInterceptor(),
41-
new XMLHttpRequestInterceptor(),
42-
new FetchInterceptor(),
43-
new WebSocketInterceptor() as any,
44-
],
50+
interceptors: Array<Interceptor<any>> = [],
4551
) {
46-
const handlersController = asyncHandlersController(handlers)
47-
super(interceptors, handlers, handlersController)
52+
this.#handlersController = asyncHandlersController(handlers)
53+
54+
this.#network = defineNetwork({
55+
sources: [
56+
new InterceptorSource({
57+
interceptors,
58+
}),
59+
],
60+
handlers,
61+
handlersController: this.#handlersController,
62+
onUnhandledFrame: fromLegacyOnUnhandledRequest(() => {
63+
return this.#listenOptions?.onUnhandledRequest
64+
}),
65+
})
4866

49-
this.handlersController = handlersController
67+
/**
68+
* @fixme This expects a readonly emitter (subset of methods).
69+
*/
70+
this.events = this.#network.events as any
71+
this.use = this.#network.use.bind(this.#network)
72+
this.resetHandlers = this.#network.resetHandlers.bind(this.#network)
73+
this.restoreHandlers = this.#network.restoreHandlers.bind(this.#network)
74+
this.listHandlers = this.#network.listHandlers.bind(this.#network)
5075
}
5176

5277
public listen(options?: Partial<ListenOptions>): void {
53-
super.listen()
78+
this.#listenOptions = options
79+
this.#network.enable()
5480

5581
if (options?.remote?.enabled) {
5682
const remoteRequestHandler = new RemoteRequestHandler({
5783
port: options.remote.port,
5884
})
5985

6086
this.#currentHandlersProxy = reversibleProxy(
61-
this.handlersController.currentHandlers,
87+
this.#handlersController.currentHandlers,
6288
{
6389
apply(target, thisArg, argArray) {
6490
return [
@@ -69,17 +95,18 @@ export class SetupServerApi
6995
},
7096
)
7197

72-
this.handlersController.currentHandlers = this.#currentHandlersProxy.proxy
98+
this.#handlersController.currentHandlers =
99+
this.#currentHandlersProxy.proxy
73100
}
74101
}
75102

76103
public boundary<Args extends Array<any>, R>(
77104
callback: (...args: Args) => R,
78105
): (...args: Args) => R {
79106
return (...args: Args): R => {
80-
return this.handlersController.context.run<any, any>(
107+
return this.#handlersController.context.run<any, any>(
81108
{
82-
initialHandlers: this.handlersController.currentHandlers(),
109+
initialHandlers: this.#handlersController.currentHandlers(),
83110
handlers: [],
84111
},
85112
callback,
@@ -89,7 +116,7 @@ export class SetupServerApi
89116
}
90117

91118
public close(): void {
92-
super.close()
119+
this.#network.disable()
93120
this.#currentHandlersProxy?.reverse()
94121
}
95122
}

0 commit comments

Comments
 (0)