File size: 6,384 Bytes
cd6720a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import { a as RequestController, o as InterceptorError } from "./fetchUtils-BKJ1XmiO.mjs";
import { DeferredPromise } from "@open-draft/deferred-promise";
import { until } from "@open-draft/until";

//#region src/utils/isPropertyAccessible.ts
/**
* A function that validates if property access is possible on an object
* without throwing. It returns `true` if the property access is possible
* and `false` otherwise.
*
* Environments like miniflare will throw on property access on certain objects
* like Request and Response, for unimplemented properties.
*/
function isPropertyAccessible(obj, key) {
	try {
		obj[key];
		return true;
	} catch {
		return false;
	}
}

//#endregion
//#region src/utils/emitAsync.ts
/**
* Emits an event on the given emitter but executes
* the listeners sequentially. This accounts for asynchronous
* listeners (e.g. those having "sleep" and handling the request).
*/
async function emitAsync(emitter, eventName, ...data) {
	const listeners = emitter.listeners(eventName);
	if (listeners.length === 0) return;
	for (const listener of listeners) await listener.apply(emitter, data);
}

//#endregion
//#region src/utils/isObject.ts
/**
* Determines if a given value is an instance of object.
*/
function isObject(value, loose = false) {
	return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]";
}

//#endregion
//#region src/utils/responseUtils.ts
/**
* Creates a generic 500 Unhandled Exception response.
*/
function createServerErrorResponse(body) {
	return new Response(JSON.stringify(body instanceof Error ? {
		name: body.name,
		message: body.message,
		stack: body.stack
	} : body), {
		status: 500,
		statusText: "Unhandled Exception",
		headers: { "Content-Type": "application/json" }
	});
}
/**
* Check if the given response is a `Response.error()`.
*
* @note Some environments, like Miniflare (Cloudflare) do not
* implement the "Response.type" property and throw on its access.
* Safely check if we can access "type" on "Response" before continuing.
* @see https://github.com/mswjs/msw/issues/1834
*/
function isResponseError(response) {
	return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error";
}
/**
* Check if the given value is a `Response` or a Response-like object.
* This is different from `value instanceof Response` because it supports
* custom `Response` constructors, like the one when using Undici directly.
*/
function isResponseLike(value) {
	return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed");
}

//#endregion
//#region src/utils/isNodeLikeError.ts
function isNodeLikeError(error) {
	if (error == null) return false;
	if (!(error instanceof Error)) return false;
	return "code" in error && "errno" in error;
}

//#endregion
//#region src/utils/handleRequest.ts
async function handleRequest(options) {
	const handleResponse = async (response) => {
		if (response instanceof Error) {
			await options.controller.errorWith(response);
			return true;
		}
		if (isResponseError(response)) {
			await options.controller.respondWith(response);
			return true;
		}
		/**
		* Handle normal responses or response-like objects.
		* @note This must come before the arbitrary object check
		* since Response instances are, in fact, objects.
		*/
		if (isResponseLike(response)) {
			await options.controller.respondWith(response);
			return true;
		}
		if (isObject(response)) {
			await options.controller.errorWith(response);
			return true;
		}
		return false;
	};
	const handleResponseError = async (error) => {
		if (error instanceof InterceptorError) throw result.error;
		if (isNodeLikeError(error)) {
			await options.controller.errorWith(error);
			return true;
		}
		if (error instanceof Response) return await handleResponse(error);
		return false;
	};
	const requestAbortPromise = new DeferredPromise();
	const onAbort = () => {
		requestAbortPromise.reject(options.request.signal?.reason);
	};
	/**
	* @note `signal` is not always defined in React Native.
	*/
	if (options.request.signal) {
		if (options.request.signal.aborted) {
			await options.controller.errorWith(options.request.signal.reason);
			return;
		}
		options.request.signal.addEventListener("abort", onAbort, { once: true });
	}
	const result = await until(async () => {
		const requestListenersPromise = emitAsync(options.emitter, "request", {
			requestId: options.requestId,
			request: options.request,
			controller: options.controller
		});
		await Promise.race([
			requestAbortPromise,
			requestListenersPromise,
			options.controller.handled
		]);
	});
	options.request.signal?.removeEventListener("abort", onAbort);
	if (requestAbortPromise.state === "rejected") {
		await options.controller.errorWith(requestAbortPromise.rejectionReason);
		return;
	}
	if (result.error) {
		if (await handleResponseError(result.error)) return;
		if (options.emitter.listenerCount("unhandledException") > 0) {
			const unhandledExceptionController = new RequestController(options.request, {
				passthrough() {},
				async respondWith(response) {
					await handleResponse(response);
				},
				async errorWith(reason) {
					/**
					* @note Handle the result of the unhandled controller
					* in the same way as the original request controller.
					* The exception here is that thrown errors within the
					* "unhandledException" event do NOT result in another
					* emit of the same event. They are forwarded as-is.
					*/
					await options.controller.errorWith(reason);
				}
			});
			await emitAsync(options.emitter, "unhandledException", {
				error: result.error,
				request: options.request,
				requestId: options.requestId,
				controller: unhandledExceptionController
			});
			if (unhandledExceptionController.readyState !== RequestController.PENDING) return;
		}
		await options.controller.respondWith(createServerErrorResponse(result.error));
		return;
	}
	if (options.controller.readyState === RequestController.PENDING) return await options.controller.passthrough();
	return options.controller.handled;
}

//#endregion
export { isPropertyAccessible as a, emitAsync as i, isResponseError as n, isObject as r, handleRequest as t };
//# sourceMappingURL=handleRequest-FIQv5pwH.mjs.map