Spaces:
Running
Running
import { | |
RequestController, | |
createServerErrorResponse, | |
emitAsync, | |
handleRequest, | |
isPropertyAccessible | |
} from "./chunk-5KMS5CTP.mjs"; | |
import { | |
FetchResponse, | |
INTERNAL_REQUEST_ID_HEADER_NAME, | |
Interceptor, | |
createRequestId | |
} from "./chunk-I7HQIBT7.mjs"; | |
// src/interceptors/ClientRequest/index.ts | |
import http2 from "http"; | |
import https2 from "https"; | |
// src/interceptors/ClientRequest/MockHttpSocket.ts | |
import net2 from "net"; | |
import { | |
HTTPParser | |
} from "_http_common"; | |
import { STATUS_CODES, IncomingMessage, ServerResponse } from "http"; | |
import { Readable } from "stream"; | |
import { invariant } from "outvariant"; | |
// src/interceptors/Socket/MockSocket.ts | |
import net from "net"; | |
// src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts | |
function normalizeSocketWriteArgs(args) { | |
const normalized = [args[0], void 0, void 0]; | |
if (typeof args[1] === "string") { | |
normalized[1] = args[1]; | |
} else if (typeof args[1] === "function") { | |
normalized[2] = args[1]; | |
} | |
if (typeof args[2] === "function") { | |
normalized[2] = args[2]; | |
} | |
return normalized; | |
} | |
// src/interceptors/Socket/MockSocket.ts | |
var MockSocket = class extends net.Socket { | |
constructor(options) { | |
super(); | |
this.options = options; | |
this.connecting = false; | |
this.connect(); | |
this._final = (callback) => { | |
callback(null); | |
}; | |
} | |
connect() { | |
this.connecting = true; | |
return this; | |
} | |
write(...args) { | |
const [chunk, encoding, callback] = normalizeSocketWriteArgs( | |
args | |
); | |
this.options.write(chunk, encoding, callback); | |
return true; | |
} | |
end(...args) { | |
const [chunk, encoding, callback] = normalizeSocketWriteArgs( | |
args | |
); | |
this.options.write(chunk, encoding, callback); | |
return super.end.apply(this, args); | |
} | |
push(chunk, encoding) { | |
this.options.read(chunk, encoding); | |
return super.push(chunk, encoding); | |
} | |
}; | |
// src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts | |
function baseUrlFromConnectionOptions(options) { | |
if ("href" in options) { | |
return new URL(options.href); | |
} | |
const protocol = options.port === 443 ? "https:" : "http:"; | |
const host = options.host; | |
const url = new URL(`${protocol}//${host}`); | |
if (options.port) { | |
url.port = options.port.toString(); | |
} | |
if (options.path) { | |
url.pathname = options.path; | |
} | |
if (options.auth) { | |
const [username, password] = options.auth.split(":"); | |
url.username = username; | |
url.password = password; | |
} | |
return url; | |
} | |
// src/interceptors/ClientRequest/utils/recordRawHeaders.ts | |
var kRawHeaders = Symbol("kRawHeaders"); | |
var kRestorePatches = Symbol("kRestorePatches"); | |
function recordRawHeader(headers, args, behavior) { | |
ensureRawHeadersSymbol(headers, []); | |
const rawHeaders = Reflect.get(headers, kRawHeaders); | |
if (behavior === "set") { | |
for (let index = rawHeaders.length - 1; index >= 0; index--) { | |
if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) { | |
rawHeaders.splice(index, 1); | |
} | |
} | |
} | |
rawHeaders.push(args); | |
} | |
function ensureRawHeadersSymbol(headers, rawHeaders) { | |
if (Reflect.has(headers, kRawHeaders)) { | |
return; | |
} | |
defineRawHeadersSymbol(headers, rawHeaders); | |
} | |
function defineRawHeadersSymbol(headers, rawHeaders) { | |
Object.defineProperty(headers, kRawHeaders, { | |
value: rawHeaders, | |
enumerable: false, | |
// Mark the symbol as configurable so its value can be overridden. | |
// Overrides happen when merging raw headers from multiple sources. | |
// E.g. new Request(new Request(url, { headers }), { headers }) | |
configurable: true | |
}); | |
} | |
function recordRawFetchHeaders() { | |
if (Reflect.get(Headers, kRestorePatches)) { | |
return Reflect.get(Headers, kRestorePatches); | |
} | |
const { | |
Headers: OriginalHeaders, | |
Request: OriginalRequest, | |
Response: OriginalResponse | |
} = globalThis; | |
const { set, append, delete: headersDeleteMethod } = Headers.prototype; | |
Object.defineProperty(Headers, kRestorePatches, { | |
value: () => { | |
Headers.prototype.set = set; | |
Headers.prototype.append = append; | |
Headers.prototype.delete = headersDeleteMethod; | |
globalThis.Headers = OriginalHeaders; | |
globalThis.Request = OriginalRequest; | |
globalThis.Response = OriginalResponse; | |
Reflect.deleteProperty(Headers, kRestorePatches); | |
}, | |
enumerable: false, | |
/** | |
* @note Mark this property as configurable | |
* so we can delete it using `Reflect.delete` during cleanup. | |
*/ | |
configurable: true | |
}); | |
Object.defineProperty(globalThis, "Headers", { | |
enumerable: true, | |
writable: true, | |
value: new Proxy(Headers, { | |
construct(target, args, newTarget) { | |
const headersInit = args[0] || []; | |
if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) { | |
const headers2 = Reflect.construct( | |
target, | |
[Reflect.get(headersInit, kRawHeaders)], | |
newTarget | |
); | |
ensureRawHeadersSymbol(headers2, [ | |
/** | |
* @note Spread the retrieved headers to clone them. | |
* This prevents multiple Headers instances from pointing | |
* at the same internal "rawHeaders" array. | |
*/ | |
...Reflect.get(headersInit, kRawHeaders) | |
]); | |
return headers2; | |
} | |
const headers = Reflect.construct(target, args, newTarget); | |
if (!Reflect.has(headers, kRawHeaders)) { | |
const rawHeadersInit = Array.isArray(headersInit) ? headersInit : Object.entries(headersInit); | |
ensureRawHeadersSymbol(headers, rawHeadersInit); | |
} | |
return headers; | |
} | |
}) | |
}); | |
Headers.prototype.set = new Proxy(Headers.prototype.set, { | |
apply(target, thisArg, args) { | |
recordRawHeader(thisArg, args, "set"); | |
return Reflect.apply(target, thisArg, args); | |
} | |
}); | |
Headers.prototype.append = new Proxy(Headers.prototype.append, { | |
apply(target, thisArg, args) { | |
recordRawHeader(thisArg, args, "append"); | |
return Reflect.apply(target, thisArg, args); | |
} | |
}); | |
Headers.prototype.delete = new Proxy(Headers.prototype.delete, { | |
apply(target, thisArg, args) { | |
const rawHeaders = Reflect.get(thisArg, kRawHeaders); | |
if (rawHeaders) { | |
for (let index = rawHeaders.length - 1; index >= 0; index--) { | |
if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) { | |
rawHeaders.splice(index, 1); | |
} | |
} | |
} | |
return Reflect.apply(target, thisArg, args); | |
} | |
}); | |
Object.defineProperty(globalThis, "Request", { | |
enumerable: true, | |
writable: true, | |
value: new Proxy(Request, { | |
construct(target, args, newTarget) { | |
const request = Reflect.construct(target, args, newTarget); | |
const inferredRawHeaders = []; | |
if (typeof args[0] === "object" && args[0].headers != null) { | |
inferredRawHeaders.push(...inferRawHeaders(args[0].headers)); | |
} | |
if (typeof args[1] === "object" && args[1].headers != null) { | |
inferredRawHeaders.push(...inferRawHeaders(args[1].headers)); | |
} | |
if (inferredRawHeaders.length > 0) { | |
ensureRawHeadersSymbol(request.headers, inferredRawHeaders); | |
} | |
return request; | |
} | |
}) | |
}); | |
Object.defineProperty(globalThis, "Response", { | |
enumerable: true, | |
writable: true, | |
value: new Proxy(Response, { | |
construct(target, args, newTarget) { | |
const response = Reflect.construct(target, args, newTarget); | |
if (typeof args[1] === "object" && args[1].headers != null) { | |
ensureRawHeadersSymbol( | |
response.headers, | |
inferRawHeaders(args[1].headers) | |
); | |
} | |
return response; | |
} | |
}) | |
}); | |
} | |
function restoreHeadersPrototype() { | |
if (!Reflect.get(Headers, kRestorePatches)) { | |
return; | |
} | |
Reflect.get(Headers, kRestorePatches)(); | |
} | |
function getRawFetchHeaders(headers) { | |
if (!Reflect.has(headers, kRawHeaders)) { | |
return Array.from(headers.entries()); | |
} | |
const rawHeaders = Reflect.get(headers, kRawHeaders); | |
return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries()); | |
} | |
function inferRawHeaders(headers) { | |
if (headers instanceof Headers) { | |
return Reflect.get(headers, kRawHeaders) || []; | |
} | |
return Reflect.get(new Headers(headers), kRawHeaders); | |
} | |
// src/interceptors/ClientRequest/MockHttpSocket.ts | |
var kRequestId = Symbol("kRequestId"); | |
var MockHttpSocket = class extends MockSocket { | |
constructor(options) { | |
super({ | |
write: (chunk, encoding, callback) => { | |
var _a; | |
if (this.socketState !== "passthrough") { | |
this.writeBuffer.push([chunk, encoding, callback]); | |
} | |
if (chunk) { | |
if (this.socketState === "passthrough") { | |
(_a = this.originalSocket) == null ? void 0 : _a.write(chunk, encoding, callback); | |
} | |
this.requestParser.execute( | |
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding) | |
); | |
} | |
}, | |
read: (chunk) => { | |
if (chunk !== null) { | |
this.responseParser.execute( | |
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) | |
); | |
} | |
} | |
}); | |
this.writeBuffer = []; | |
this.socketState = "unknown"; | |
this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => { | |
var _a; | |
this.shouldKeepAlive = shouldKeepAlive; | |
const url = new URL(path, this.baseUrl); | |
const method = ((_a = this.connectionOptions.method) == null ? void 0 : _a.toUpperCase()) || "GET"; | |
const headers = FetchResponse.parseRawHeaders(rawHeaders); | |
const canHaveBody = method !== "GET" && method !== "HEAD"; | |
if (url.username || url.password) { | |
if (!headers.has("authorization")) { | |
headers.set("authorization", `Basic ${url.username}:${url.password}`); | |
} | |
url.username = ""; | |
url.password = ""; | |
} | |
if (canHaveBody) { | |
this.requestStream = new Readable({ | |
/** | |
* @note Provide the `read()` method so a `Readable` could be | |
* used as the actual request body (the stream calls "read()"). | |
* We control the queue in the onRequestBody/End functions. | |
*/ | |
read: () => { | |
this.flushWriteBuffer(); | |
} | |
}); | |
} | |
const requestId = createRequestId(); | |
this.request = new Request(url, { | |
method, | |
headers, | |
credentials: "same-origin", | |
// @ts-expect-error Undocumented Fetch property. | |
duplex: canHaveBody ? "half" : void 0, | |
body: canHaveBody ? Readable.toWeb(this.requestStream) : null | |
}); | |
Reflect.set(this.request, kRequestId, requestId); | |
if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) { | |
this.passthrough(); | |
return; | |
} | |
this.onRequest({ | |
requestId, | |
request: this.request, | |
socket: this | |
}); | |
}; | |
this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => { | |
const headers = FetchResponse.parseRawHeaders(rawHeaders); | |
const response = new FetchResponse( | |
/** | |
* @note The Fetch API response instance exposed to the consumer | |
* is created over the response stream of the HTTP parser. It is NOT | |
* related to the Socket instance. This way, you can read response body | |
* in response listener while the Socket instance delays the emission | |
* of "end" and other events until those response listeners are finished. | |
*/ | |
FetchResponse.isResponseWithBody(status) ? Readable.toWeb( | |
this.responseStream = new Readable({ read() { | |
} }) | |
) : null, | |
{ | |
url, | |
status, | |
statusText, | |
headers | |
} | |
); | |
invariant( | |
this.request, | |
"Failed to handle a response: request does not exist" | |
); | |
if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) { | |
return; | |
} | |
this.responseListenersPromise = this.onResponse({ | |
response, | |
isMockedResponse: this.socketState === "mock", | |
requestId: Reflect.get(this.request, kRequestId), | |
request: this.request, | |
socket: this | |
}); | |
}; | |
this.connectionOptions = options.connectionOptions; | |
this.createConnection = options.createConnection; | |
this.onRequest = options.onRequest; | |
this.onResponse = options.onResponse; | |
this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions); | |
this.requestParser = new HTTPParser(); | |
this.requestParser.initialize(HTTPParser.REQUEST, {}); | |
this.requestParser[HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this); | |
this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this); | |
this.requestParser[HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this); | |
this.responseParser = new HTTPParser(); | |
this.responseParser.initialize(HTTPParser.RESPONSE, {}); | |
this.responseParser[HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this); | |
this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this); | |
this.responseParser[HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this); | |
this.once("finish", () => this.requestParser.free()); | |
if (this.baseUrl.protocol === "https:") { | |
Reflect.set(this, "encrypted", true); | |
Reflect.set(this, "authorized", false); | |
Reflect.set(this, "getProtocol", () => "TLSv1.3"); | |
Reflect.set(this, "getSession", () => void 0); | |
Reflect.set(this, "isSessionReused", () => false); | |
} | |
} | |
emit(event, ...args) { | |
const emitEvent = super.emit.bind(this, event, ...args); | |
if (this.responseListenersPromise) { | |
this.responseListenersPromise.finally(emitEvent); | |
return this.listenerCount(event) > 0; | |
} | |
return emitEvent(); | |
} | |
destroy(error) { | |
this.responseParser.free(); | |
if (error) { | |
this.emit("error", error); | |
} | |
return super.destroy(error); | |
} | |
/** | |
* Establish this Socket connection as-is and pipe | |
* its data/events through this Socket. | |
*/ | |
passthrough() { | |
this.socketState = "passthrough"; | |
if (this.destroyed) { | |
return; | |
} | |
const socket = this.createConnection(); | |
this.originalSocket = socket; | |
this.once("error", (error) => { | |
socket.destroy(error); | |
}); | |
this.address = socket.address.bind(socket); | |
let writeArgs; | |
let headersWritten = false; | |
while (writeArgs = this.writeBuffer.shift()) { | |
if (writeArgs !== void 0) { | |
if (!headersWritten) { | |
const [chunk, encoding, callback] = writeArgs; | |
const chunkString = chunk.toString(); | |
const chunkBeforeRequestHeaders = chunkString.slice( | |
0, | |
chunkString.indexOf("\r\n") + 2 | |
); | |
const chunkAfterRequestHeaders = chunkString.slice( | |
chunk.indexOf("\r\n\r\n") | |
); | |
const rawRequestHeaders = getRawFetchHeaders(this.request.headers); | |
const requestHeadersString = rawRequestHeaders.filter(([name]) => { | |
return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME; | |
}).map(([name, value]) => `${name}: ${value}`).join("\r\n"); | |
const headersChunk = `${chunkBeforeRequestHeaders}${requestHeadersString}${chunkAfterRequestHeaders}`; | |
socket.write(headersChunk, encoding, callback); | |
headersWritten = true; | |
continue; | |
} | |
socket.write(...writeArgs); | |
} | |
} | |
if (Reflect.get(socket, "encrypted")) { | |
const tlsProperties = [ | |
"encrypted", | |
"authorized", | |
"getProtocol", | |
"getSession", | |
"isSessionReused" | |
]; | |
tlsProperties.forEach((propertyName) => { | |
Object.defineProperty(this, propertyName, { | |
enumerable: true, | |
get: () => { | |
const value = Reflect.get(socket, propertyName); | |
return typeof value === "function" ? value.bind(socket) : value; | |
} | |
}); | |
}); | |
} | |
socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => { | |
this.connecting = socket.connecting; | |
this.emit("connect"); | |
}).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => { | |
this.push(chunk); | |
}).on("error", (error) => { | |
Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError")); | |
this.emit("error", error); | |
}).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end")); | |
} | |
/** | |
* Convert the given Fetch API `Response` instance to an | |
* HTTP message and push it to the socket. | |
*/ | |
async respondWith(response) { | |
var _a; | |
if (this.destroyed) { | |
return; | |
} | |
if (isPropertyAccessible(response, "type") && response.type === "error") { | |
this.errorWith(new TypeError("Network error")); | |
return; | |
} | |
this.mockConnect(); | |
this.socketState = "mock"; | |
this.flushWriteBuffer(); | |
const serverResponse = new ServerResponse(new IncomingMessage(this)); | |
serverResponse.assignSocket( | |
new MockSocket({ | |
write: (chunk, encoding, callback) => { | |
this.push(chunk, encoding); | |
callback == null ? void 0 : callback(); | |
}, | |
read() { | |
} | |
}) | |
); | |
serverResponse.removeHeader("connection"); | |
serverResponse.removeHeader("date"); | |
const rawResponseHeaders = getRawFetchHeaders(response.headers); | |
serverResponse.writeHead( | |
response.status, | |
response.statusText || STATUS_CODES[response.status], | |
rawResponseHeaders | |
); | |
this.once("error", () => { | |
serverResponse.destroy(); | |
}); | |
if (response.body) { | |
try { | |
const reader = response.body.getReader(); | |
while (true) { | |
const { done, value } = await reader.read(); | |
if (done) { | |
serverResponse.end(); | |
break; | |
} | |
serverResponse.write(value); | |
} | |
} catch (error) { | |
this.respondWith(createServerErrorResponse(error)); | |
return; | |
} | |
} else { | |
serverResponse.end(); | |
} | |
if (!this.shouldKeepAlive) { | |
this.emit("readable"); | |
(_a = this.responseStream) == null ? void 0 : _a.push(null); | |
this.push(null); | |
} | |
} | |
/** | |
* Close this socket connection with the given error. | |
*/ | |
errorWith(error) { | |
this.destroy(error); | |
} | |
mockConnect() { | |
this.connecting = false; | |
const isIPv6 = net2.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6; | |
const addressInfo = { | |
address: isIPv6 ? "::1" : "127.0.0.1", | |
family: isIPv6 ? "IPv6" : "IPv4", | |
port: this.connectionOptions.port | |
}; | |
this.address = () => addressInfo; | |
this.emit( | |
"lookup", | |
null, | |
addressInfo.address, | |
addressInfo.family === "IPv6" ? 6 : 4, | |
this.connectionOptions.host | |
); | |
this.emit("connect"); | |
this.emit("ready"); | |
if (this.baseUrl.protocol === "https:") { | |
this.emit("secure"); | |
this.emit("secureConnect"); | |
this.emit( | |
"session", | |
this.connectionOptions.session || Buffer.from("mock-session-renegotiate") | |
); | |
this.emit("session", Buffer.from("mock-session-resume")); | |
} | |
} | |
flushWriteBuffer() { | |
for (const writeCall of this.writeBuffer) { | |
if (typeof writeCall[2] === "function") { | |
writeCall[2](); | |
writeCall[2] = void 0; | |
} | |
} | |
} | |
onRequestBody(chunk) { | |
invariant( | |
this.requestStream, | |
"Failed to write to a request stream: stream does not exist" | |
); | |
this.requestStream.push(chunk); | |
} | |
onRequestEnd() { | |
if (this.requestStream) { | |
this.requestStream.push(null); | |
} | |
} | |
onResponseBody(chunk) { | |
invariant( | |
this.responseStream, | |
"Failed to write to a response stream: stream does not exist" | |
); | |
this.responseStream.push(chunk); | |
} | |
onResponseEnd() { | |
if (this.responseStream) { | |
this.responseStream.push(null); | |
} | |
} | |
}; | |
// src/interceptors/ClientRequest/agents.ts | |
import http from "http"; | |
import https from "https"; | |
var MockAgent = class extends http.Agent { | |
constructor(options) { | |
super(); | |
this.customAgent = options.customAgent; | |
this.onRequest = options.onRequest; | |
this.onResponse = options.onResponse; | |
} | |
createConnection(options, callback) { | |
const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection; | |
const createConnectionOptions = this.customAgent instanceof http.Agent ? { | |
...options, | |
...this.customAgent.options | |
} : options; | |
const socket = new MockHttpSocket({ | |
connectionOptions: options, | |
createConnection: createConnection.bind( | |
this.customAgent || this, | |
createConnectionOptions, | |
callback | |
), | |
onRequest: this.onRequest.bind(this), | |
onResponse: this.onResponse.bind(this) | |
}); | |
return socket; | |
} | |
}; | |
var MockHttpsAgent = class extends https.Agent { | |
constructor(options) { | |
super(); | |
this.customAgent = options.customAgent; | |
this.onRequest = options.onRequest; | |
this.onResponse = options.onResponse; | |
} | |
createConnection(options, callback) { | |
const createConnection = this.customAgent instanceof https.Agent ? this.customAgent.createConnection : super.createConnection; | |
const createConnectionOptions = this.customAgent instanceof https.Agent ? { | |
...options, | |
...this.customAgent.options | |
} : options; | |
const socket = new MockHttpSocket({ | |
connectionOptions: options, | |
createConnection: createConnection.bind( | |
this.customAgent || this, | |
createConnectionOptions, | |
callback | |
), | |
onRequest: this.onRequest.bind(this), | |
onResponse: this.onResponse.bind(this) | |
}); | |
return socket; | |
} | |
}; | |
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts | |
import { urlToHttpOptions } from "url"; | |
import { | |
Agent as HttpAgent, | |
globalAgent as httpGlobalAgent | |
} from "http"; | |
import { | |
Agent as HttpsAgent, | |
globalAgent as httpsGlobalAgent | |
} from "https"; | |
import { | |
URL as URL2, | |
parse as parseUrl | |
} from "url"; | |
import { Logger as Logger3 } from "@open-draft/logger"; | |
// src/utils/getUrlByRequestOptions.ts | |
import { Agent } from "http"; | |
import { Logger } from "@open-draft/logger"; | |
var logger = new Logger("utils getUrlByRequestOptions"); | |
var DEFAULT_PATH = "/"; | |
var DEFAULT_PROTOCOL = "http:"; | |
var DEFAULT_HOSTNAME = "localhost"; | |
var SSL_PORT = 443; | |
function getAgent(options) { | |
return options.agent instanceof Agent ? options.agent : void 0; | |
} | |
function getProtocolByRequestOptions(options) { | |
var _a; | |
if (options.protocol) { | |
return options.protocol; | |
} | |
const agent = getAgent(options); | |
const agentProtocol = agent == null ? void 0 : agent.protocol; | |
if (agentProtocol) { | |
return agentProtocol; | |
} | |
const port = getPortByRequestOptions(options); | |
const isSecureRequest = options.cert || port === SSL_PORT; | |
return isSecureRequest ? "https:" : ((_a = options.uri) == null ? void 0 : _a.protocol) || DEFAULT_PROTOCOL; | |
} | |
function getPortByRequestOptions(options) { | |
if (options.port) { | |
return Number(options.port); | |
} | |
const agent = getAgent(options); | |
if (agent == null ? void 0 : agent.options.port) { | |
return Number(agent.options.port); | |
} | |
if (agent == null ? void 0 : agent.defaultPort) { | |
return Number(agent.defaultPort); | |
} | |
return void 0; | |
} | |
function getAuthByRequestOptions(options) { | |
if (options.auth) { | |
const [username, password] = options.auth.split(":"); | |
return { username, password }; | |
} | |
} | |
function isRawIPv6Address(host) { | |
return host.includes(":") && !host.startsWith("[") && !host.endsWith("]"); | |
} | |
function getHostname(options) { | |
let host = options.hostname || options.host; | |
if (host) { | |
if (isRawIPv6Address(host)) { | |
host = `[${host}]`; | |
} | |
return new URL(`http://${host}`).hostname; | |
} | |
return DEFAULT_HOSTNAME; | |
} | |
function getUrlByRequestOptions(options) { | |
logger.info("request options", options); | |
if (options.uri) { | |
logger.info( | |
'constructing url from explicitly provided "options.uri": %s', | |
options.uri | |
); | |
return new URL(options.uri.href); | |
} | |
logger.info("figuring out url from request options..."); | |
const protocol = getProtocolByRequestOptions(options); | |
logger.info("protocol", protocol); | |
const port = getPortByRequestOptions(options); | |
logger.info("port", port); | |
const hostname = getHostname(options); | |
logger.info("hostname", hostname); | |
const path = options.path || DEFAULT_PATH; | |
logger.info("path", path); | |
const credentials = getAuthByRequestOptions(options); | |
logger.info("credentials", credentials); | |
const authString = credentials ? `${credentials.username}:${credentials.password}@` : ""; | |
logger.info("auth string:", authString); | |
const portString = typeof port !== "undefined" ? `:${port}` : ""; | |
const url = new URL(`${protocol}//${hostname}${portString}${path}`); | |
url.username = (credentials == null ? void 0 : credentials.username) || ""; | |
url.password = (credentials == null ? void 0 : credentials.password) || ""; | |
logger.info("created url:", url); | |
return url; | |
} | |
// src/utils/cloneObject.ts | |
import { Logger as Logger2 } from "@open-draft/logger"; | |
var logger2 = new Logger2("cloneObject"); | |
function isPlainObject(obj) { | |
var _a; | |
logger2.info("is plain object?", obj); | |
if (obj == null || !((_a = obj.constructor) == null ? void 0 : _a.name)) { | |
logger2.info("given object is undefined, not a plain object..."); | |
return false; | |
} | |
logger2.info("checking the object constructor:", obj.constructor.name); | |
return obj.constructor.name === "Object"; | |
} | |
function cloneObject(obj) { | |
logger2.info("cloning object:", obj); | |
const enumerableProperties = Object.entries(obj).reduce( | |
(acc, [key, value]) => { | |
logger2.info("analyzing key-value pair:", key, value); | |
acc[key] = isPlainObject(value) ? cloneObject(value) : value; | |
return acc; | |
}, | |
{} | |
); | |
return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties); | |
} | |
// src/utils/isObject.ts | |
function isObject(value, loose = false) { | |
return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | |
} | |
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts | |
var logger3 = new Logger3("http normalizeClientRequestArgs"); | |
function resolveRequestOptions(args, url) { | |
if (typeof args[1] === "undefined" || typeof args[1] === "function") { | |
logger3.info("request options not provided, deriving from the url", url); | |
return urlToHttpOptions(url); | |
} | |
if (args[1]) { | |
logger3.info("has custom RequestOptions!", args[1]); | |
const requestOptionsFromUrl = urlToHttpOptions(url); | |
logger3.info("derived RequestOptions from the URL:", requestOptionsFromUrl); | |
logger3.info("cloning RequestOptions..."); | |
const clonedRequestOptions = cloneObject(args[1]); | |
logger3.info("successfully cloned RequestOptions!", clonedRequestOptions); | |
return { | |
...requestOptionsFromUrl, | |
...clonedRequestOptions | |
}; | |
} | |
logger3.info("using an empty object as request options"); | |
return {}; | |
} | |
function overrideUrlByRequestOptions(url, options) { | |
url.host = options.host || url.host; | |
url.hostname = options.hostname || url.hostname; | |
url.port = options.port ? options.port.toString() : url.port; | |
if (options.path) { | |
const parsedOptionsPath = parseUrl(options.path, false); | |
url.pathname = parsedOptionsPath.pathname || ""; | |
url.search = parsedOptionsPath.search || ""; | |
} | |
return url; | |
} | |
function resolveCallback(args) { | |
return typeof args[1] === "function" ? args[1] : args[2]; | |
} | |
function normalizeClientRequestArgs(defaultProtocol, args) { | |
let url; | |
let options; | |
let callback; | |
logger3.info("arguments", args); | |
logger3.info("using default protocol:", defaultProtocol); | |
if (args.length === 0) { | |
const url2 = new URL2("http://localhost"); | |
const options2 = resolveRequestOptions(args, url2); | |
return [url2, options2]; | |
} | |
if (typeof args[0] === "string") { | |
logger3.info("first argument is a location string:", args[0]); | |
url = new URL2(args[0]); | |
logger3.info("created a url:", url); | |
const requestOptionsFromUrl = urlToHttpOptions(url); | |
logger3.info("request options from url:", requestOptionsFromUrl); | |
options = resolveRequestOptions(args, url); | |
logger3.info("resolved request options:", options); | |
callback = resolveCallback(args); | |
} else if (args[0] instanceof URL2) { | |
url = args[0]; | |
logger3.info("first argument is a URL:", url); | |
if (typeof args[1] !== "undefined" && isObject(args[1])) { | |
url = overrideUrlByRequestOptions(url, args[1]); | |
} | |
options = resolveRequestOptions(args, url); | |
logger3.info("derived request options:", options); | |
callback = resolveCallback(args); | |
} else if ("hash" in args[0] && !("method" in args[0])) { | |
const [legacyUrl] = args; | |
logger3.info("first argument is a legacy URL:", legacyUrl); | |
if (legacyUrl.hostname === null) { | |
logger3.info("given legacy URL is relative (no hostname)"); | |
return isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [ | |
{ path: legacyUrl.path, ...args[1] }, | |
args[2] | |
]) : normalizeClientRequestArgs(defaultProtocol, [ | |
{ path: legacyUrl.path }, | |
args[1] | |
]); | |
} | |
logger3.info("given legacy url is absolute"); | |
const resolvedUrl = new URL2(legacyUrl.href); | |
return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [ | |
resolvedUrl, | |
args[1], | |
args[2] | |
]); | |
} else if (isObject(args[0])) { | |
options = { ...args[0] }; | |
logger3.info("first argument is RequestOptions:", options); | |
options.protocol = options.protocol || defaultProtocol; | |
logger3.info("normalized request options:", options); | |
url = getUrlByRequestOptions(options); | |
logger3.info("created a URL from RequestOptions:", url.href); | |
callback = resolveCallback(args); | |
} else { | |
throw new Error( | |
`Failed to construct ClientRequest with these parameters: ${args}` | |
); | |
} | |
options.protocol = options.protocol || url.protocol; | |
options.method = options.method || "GET"; | |
if (typeof options.agent === "undefined") { | |
const agent = options.protocol === "https:" ? new HttpsAgent({ | |
// Any other value other than false is considered as true, so we don't add this property if undefined. | |
..."rejectUnauthorized" in options && { | |
rejectUnauthorized: options.rejectUnauthorized | |
} | |
}) : new HttpAgent(); | |
options.agent = agent; | |
logger3.info("resolved fallback agent:", agent); | |
} | |
if (!options._defaultAgent) { | |
logger3.info( | |
'has no default agent, setting the default agent for "%s"', | |
options.protocol | |
); | |
options._defaultAgent = options.protocol === "https:" ? httpsGlobalAgent : httpGlobalAgent; | |
} | |
logger3.info("successfully resolved url:", url.href); | |
logger3.info("successfully resolved options:", options); | |
logger3.info("successfully resolved callback:", callback); | |
if (!(url instanceof URL2)) { | |
url = url.toString(); | |
} | |
return [url, options, callback]; | |
} | |
// src/interceptors/ClientRequest/index.ts | |
var _ClientRequestInterceptor = class extends Interceptor { | |
constructor() { | |
super(_ClientRequestInterceptor.symbol); | |
this.onRequest = async ({ | |
request, | |
socket | |
}) => { | |
const requestId = Reflect.get(request, kRequestId); | |
const controller = new RequestController(request); | |
const isRequestHandled = await handleRequest({ | |
request, | |
requestId, | |
controller, | |
emitter: this.emitter, | |
onResponse: (response) => { | |
socket.respondWith(response); | |
}, | |
onRequestError: (response) => { | |
socket.respondWith(response); | |
}, | |
onError: (error) => { | |
if (error instanceof Error) { | |
socket.errorWith(error); | |
} | |
} | |
}); | |
if (!isRequestHandled) { | |
return socket.passthrough(); | |
} | |
}; | |
this.onResponse = async ({ | |
requestId, | |
request, | |
response, | |
isMockedResponse | |
}) => { | |
return emitAsync(this.emitter, "response", { | |
requestId, | |
request, | |
response, | |
isMockedResponse | |
}); | |
}; | |
} | |
setup() { | |
const { get: originalGet, request: originalRequest } = http2; | |
const { get: originalHttpsGet, request: originalHttpsRequest } = https2; | |
const onRequest = this.onRequest.bind(this); | |
const onResponse = this.onResponse.bind(this); | |
http2.request = new Proxy(http2.request, { | |
apply: (target, thisArg, args) => { | |
const [url, options, callback] = normalizeClientRequestArgs( | |
"http:", | |
args | |
); | |
const mockAgent = new MockAgent({ | |
customAgent: options.agent, | |
onRequest, | |
onResponse | |
}); | |
options.agent = mockAgent; | |
return Reflect.apply(target, thisArg, [url, options, callback]); | |
} | |
}); | |
http2.get = new Proxy(http2.get, { | |
apply: (target, thisArg, args) => { | |
const [url, options, callback] = normalizeClientRequestArgs( | |
"http:", | |
args | |
); | |
const mockAgent = new MockAgent({ | |
customAgent: options.agent, | |
onRequest, | |
onResponse | |
}); | |
options.agent = mockAgent; | |
return Reflect.apply(target, thisArg, [url, options, callback]); | |
} | |
}); | |
https2.request = new Proxy(https2.request, { | |
apply: (target, thisArg, args) => { | |
const [url, options, callback] = normalizeClientRequestArgs( | |
"https:", | |
args | |
); | |
const mockAgent = new MockHttpsAgent({ | |
customAgent: options.agent, | |
onRequest, | |
onResponse | |
}); | |
options.agent = mockAgent; | |
return Reflect.apply(target, thisArg, [url, options, callback]); | |
} | |
}); | |
https2.get = new Proxy(https2.get, { | |
apply: (target, thisArg, args) => { | |
const [url, options, callback] = normalizeClientRequestArgs( | |
"https:", | |
args | |
); | |
const mockAgent = new MockHttpsAgent({ | |
customAgent: options.agent, | |
onRequest, | |
onResponse | |
}); | |
options.agent = mockAgent; | |
return Reflect.apply(target, thisArg, [url, options, callback]); | |
} | |
}); | |
recordRawFetchHeaders(); | |
this.subscriptions.push(() => { | |
http2.get = originalGet; | |
http2.request = originalRequest; | |
https2.get = originalHttpsGet; | |
https2.request = originalHttpsRequest; | |
restoreHeadersPrototype(); | |
}); | |
} | |
}; | |
var ClientRequestInterceptor = _ClientRequestInterceptor; | |
ClientRequestInterceptor.symbol = Symbol("client-request-interceptor"); | |
export { | |
ClientRequestInterceptor | |
}; | |
//# sourceMappingURL=chunk-FWJSC2QD.mjs.map |