修改后台权限

This commit is contained in:
yoyuzh
2026-03-24 14:30:59 +08:00
parent 00f902f475
commit b2d9db7be9
9310 changed files with 1246063 additions and 48 deletions

View File

@@ -0,0 +1,65 @@
import { Logger } from "@open-draft/logger";
import { Emitter, Listener } from "strict-event-emitter";
//#region src/Interceptor.d.ts
type InterceptorEventMap = Record<string, any>;
type InterceptorSubscription = () => void;
/**
* Request header name to detect when a single request
* is being handled by nested interceptors (XHR -> ClientRequest).
* Obscure by design to prevent collisions with user-defined headers.
* Ideally, come up with the Interceptor-level mechanism for this.
* @see https://github.com/mswjs/interceptors/issues/378
*/
declare const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id";
declare function getGlobalSymbol<V>(symbol: Symbol): V | undefined;
declare function deleteGlobalSymbol(symbol: Symbol): void;
declare enum InterceptorReadyState {
INACTIVE = "INACTIVE",
APPLYING = "APPLYING",
APPLIED = "APPLIED",
DISPOSING = "DISPOSING",
DISPOSED = "DISPOSED",
}
type ExtractEventNames<Events extends Record<string, any>> = Events extends Record<infer EventName, any> ? EventName : never;
declare class Interceptor<Events extends InterceptorEventMap> {
private readonly symbol;
protected emitter: Emitter<Events>;
protected subscriptions: Array<InterceptorSubscription>;
protected logger: Logger;
readyState: InterceptorReadyState;
constructor(symbol: symbol);
/**
* Determine if this interceptor can be applied
* in the current environment.
*/
protected checkEnvironment(): boolean;
/**
* Apply this interceptor to the current process.
* Returns an already running interceptor instance if it's present.
*/
apply(): void;
/**
* Setup the module augments and stubs necessary for this interceptor.
* This method is not run if there's a running interceptor instance
* to prevent instantiating an interceptor multiple times.
*/
protected setup(): void;
/**
* Listen to the interceptor's public events.
*/
on<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
once<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
off<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
removeAllListeners<EventName extends ExtractEventNames<Events>>(event?: EventName): this;
/**
* Disposes of any side-effects this interceptor has introduced.
*/
dispose(): void;
private getInstance;
private setInstance;
private clearInstance;
}
//#endregion
export { InterceptorReadyState as a, getGlobalSymbol as c, InterceptorEventMap as i, INTERNAL_REQUEST_ID_HEADER_NAME as n, InterceptorSubscription as o, Interceptor as r, deleteGlobalSymbol as s, ExtractEventNames as t };
//# sourceMappingURL=Interceptor-Deczogc8.d.cts.map

View File

@@ -0,0 +1,65 @@
import { Logger } from "@open-draft/logger";
import { Emitter, Listener } from "strict-event-emitter";
//#region src/Interceptor.d.ts
type InterceptorEventMap = Record<string, any>;
type InterceptorSubscription = () => void;
/**
* Request header name to detect when a single request
* is being handled by nested interceptors (XHR -> ClientRequest).
* Obscure by design to prevent collisions with user-defined headers.
* Ideally, come up with the Interceptor-level mechanism for this.
* @see https://github.com/mswjs/interceptors/issues/378
*/
declare const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id";
declare function getGlobalSymbol<V>(symbol: Symbol): V | undefined;
declare function deleteGlobalSymbol(symbol: Symbol): void;
declare enum InterceptorReadyState {
INACTIVE = "INACTIVE",
APPLYING = "APPLYING",
APPLIED = "APPLIED",
DISPOSING = "DISPOSING",
DISPOSED = "DISPOSED",
}
type ExtractEventNames<Events extends Record<string, any>> = Events extends Record<infer EventName, any> ? EventName : never;
declare class Interceptor<Events extends InterceptorEventMap> {
private readonly symbol;
protected emitter: Emitter<Events>;
protected subscriptions: Array<InterceptorSubscription>;
protected logger: Logger;
readyState: InterceptorReadyState;
constructor(symbol: symbol);
/**
* Determine if this interceptor can be applied
* in the current environment.
*/
protected checkEnvironment(): boolean;
/**
* Apply this interceptor to the current process.
* Returns an already running interceptor instance if it's present.
*/
apply(): void;
/**
* Setup the module augments and stubs necessary for this interceptor.
* This method is not run if there's a running interceptor instance
* to prevent instantiating an interceptor multiple times.
*/
protected setup(): void;
/**
* Listen to the interceptor's public events.
*/
on<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
once<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
off<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
removeAllListeners<EventName extends ExtractEventNames<Events>>(event?: EventName): this;
/**
* Disposes of any side-effects this interceptor has introduced.
*/
dispose(): void;
private getInstance;
private setInstance;
private clearInstance;
}
//#endregion
export { InterceptorReadyState as a, getGlobalSymbol as c, InterceptorEventMap as i, INTERNAL_REQUEST_ID_HEADER_NAME as n, InterceptorSubscription as o, Interceptor as r, deleteGlobalSymbol as s, ExtractEventNames as t };
//# sourceMappingURL=Interceptor-gqKgs-aF.d.mts.map

View File

@@ -0,0 +1,761 @@
const require_getRawRequest = require('./getRawRequest-zx8rUJL2.cjs');
const require_createRequestId = require('./createRequestId-Cs4oXfa1.cjs');
const require_bufferUtils = require('./bufferUtils-Uc0eRItL.cjs');
const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BvCTG97d.cjs');
const require_handleRequest = require('./handleRequest-CvX2G-Lz.cjs');
let outvariant = require("outvariant");
let is_node_process = require("is-node-process");
//#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts
/**
* Concatenate two `Uint8Array` buffers.
*/
function concatArrayBuffer(left, right) {
const result = new Uint8Array(left.byteLength + right.byteLength);
result.set(left, 0);
result.set(right, left.byteLength);
return result;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts
var EventPolyfill = class {
constructor(type, options) {
this.NONE = 0;
this.CAPTURING_PHASE = 1;
this.AT_TARGET = 2;
this.BUBBLING_PHASE = 3;
this.type = "";
this.srcElement = null;
this.currentTarget = null;
this.eventPhase = 0;
this.isTrusted = true;
this.composed = false;
this.cancelable = true;
this.defaultPrevented = false;
this.bubbles = true;
this.lengthComputable = true;
this.loaded = 0;
this.total = 0;
this.cancelBubble = false;
this.returnValue = true;
this.type = type;
this.target = options?.target || null;
this.currentTarget = options?.currentTarget || null;
this.timeStamp = Date.now();
}
composedPath() {
return [];
}
initEvent(type, bubbles, cancelable) {
this.type = type;
this.bubbles = !!bubbles;
this.cancelable = !!cancelable;
}
preventDefault() {
this.defaultPrevented = true;
}
stopPropagation() {}
stopImmediatePropagation() {}
};
//#endregion
//#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts
var ProgressEventPolyfill = class extends EventPolyfill {
constructor(type, init) {
super(type);
this.lengthComputable = init?.lengthComputable || false;
this.composed = init?.composed || false;
this.loaded = init?.loaded || 0;
this.total = init?.total || 0;
}
};
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/createEvent.ts
const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined";
function createEvent(target, type, init) {
const progressEvents = [
"error",
"progress",
"loadstart",
"loadend",
"load",
"timeout",
"abort"
];
/**
* `ProgressEvent` is not supported in React Native.
* @see https://github.com/mswjs/interceptors/issues/40
*/
const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill;
return progressEvents.includes(type) ? new ProgressEventClass(type, {
lengthComputable: true,
loaded: init?.loaded || 0,
total: init?.total || 0
}) : new EventPolyfill(type, {
target,
currentTarget: target
});
}
//#endregion
//#region src/utils/findPropertySource.ts
/**
* Returns the source object of the given property on the target object
* (the target itself, any parent in its prototype, or null).
*/
function findPropertySource(target, propertyName) {
if (!(propertyName in target)) return null;
if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target;
const prototype = Reflect.getPrototypeOf(target);
return prototype ? findPropertySource(prototype, propertyName) : null;
}
//#endregion
//#region src/utils/createProxy.ts
function createProxy(target, options) {
return new Proxy(target, optionsToProxyHandler(options));
}
function optionsToProxyHandler(options) {
const { constructorCall, methodCall, getProperty, setProperty } = options;
const handler = {};
if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) {
const next = Reflect.construct.bind(null, target, args, newTarget);
return constructorCall.call(newTarget, args, next);
};
handler.set = function(target, propertyName, nextValue) {
const next = () => {
const propertySource = findPropertySource(target, propertyName) || target;
const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName);
if (typeof ownDescriptors?.set !== "undefined") {
ownDescriptors.set.apply(target, [nextValue]);
return true;
}
return Reflect.defineProperty(propertySource, propertyName, {
writable: true,
enumerable: true,
configurable: true,
value: nextValue
});
};
if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next);
return next();
};
handler.get = function(target, propertyName, receiver) {
/**
* @note Using `Reflect.get()` here causes "TypeError: Illegal invocation".
*/
const next = () => target[propertyName];
const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next();
if (typeof value === "function") return (...args) => {
const next$1 = value.bind(target, ...args);
if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1);
return next$1();
};
return value;
};
return handler;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts
function isDomParserSupportedType(type) {
return [
"application/xhtml+xml",
"application/xml",
"image/svg+xml",
"text/html",
"text/xml"
].some((supportedType) => {
return type.startsWith(supportedType);
});
}
//#endregion
//#region src/utils/parseJson.ts
/**
* Parses a given string into JSON.
* Gracefully handles invalid JSON by returning `null`.
*/
function parseJson(data) {
try {
return JSON.parse(data);
} catch (_) {
return null;
}
}
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/createResponse.ts
/**
* Creates a Fetch API `Response` instance from the given
* `XMLHttpRequest` instance and a response body.
*/
function createResponse(request, body) {
return new require_getRawRequest.FetchResponse(require_getRawRequest.FetchResponse.isResponseWithBody(request.status) ? body : null, {
url: request.responseURL,
status: request.status,
statusText: request.statusText,
headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders())
});
}
function createHeadersFromXMLHttpRequestHeaders(headersString) {
const headers = new Headers();
const lines = headersString.split(/[\r\n]+/);
for (const line of lines) {
if (line.trim() === "") continue;
const [name, ...parts] = line.split(": ");
const value = parts.join(": ");
headers.append(name, value);
}
return headers;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts
/**
* Return a total byte length of the given request/response body.
* If the `Content-Length` header is present, it will be used as the byte length.
*/
async function getBodyByteLength(input) {
const explicitContentLength = input.headers.get("content-length");
if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength);
return (await input.arrayBuffer()).byteLength;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts
const kIsRequestHandled = Symbol("kIsRequestHandled");
const IS_NODE = (0, is_node_process.isNodeProcess)();
const kFetchRequest = Symbol("kFetchRequest");
/**
* An `XMLHttpRequest` instance controller that allows us
* to handle any given request instance (e.g. responding to it).
*/
var XMLHttpRequestController = class {
constructor(initialRequest, logger) {
this.initialRequest = initialRequest;
this.logger = logger;
this.method = "GET";
this.url = null;
this[kIsRequestHandled] = false;
this.events = /* @__PURE__ */ new Map();
this.uploadEvents = /* @__PURE__ */ new Map();
this.requestId = require_createRequestId.createRequestId();
this.requestHeaders = new Headers();
this.responseBuffer = new Uint8Array();
this.request = createProxy(initialRequest, {
setProperty: ([propertyName, nextValue], invoke) => {
switch (propertyName) {
case "ontimeout": {
const eventName = propertyName.slice(2);
/**
* @note Proxy callbacks to event listeners because JSDOM has trouble
* translating these properties to callbacks. It seemed to be operating
* on events exclusively.
*/
this.request.addEventListener(eventName, nextValue);
return invoke();
}
default: return invoke();
}
},
methodCall: ([methodName, args], invoke) => {
switch (methodName) {
case "open": {
const [method, url] = args;
if (typeof url === "undefined") {
this.method = "GET";
this.url = toAbsoluteUrl(method);
} else {
this.method = method;
this.url = toAbsoluteUrl(url);
}
this.logger = this.logger.extend(`${this.method} ${this.url.href}`);
this.logger.info("open", this.method, this.url.href);
return invoke();
}
case "addEventListener": {
const [eventName, listener] = args;
this.registerEvent(eventName, listener);
this.logger.info("addEventListener", eventName, listener);
return invoke();
}
case "setRequestHeader": {
const [name, value] = args;
this.requestHeaders.set(name, value);
this.logger.info("setRequestHeader", name, value);
return invoke();
}
case "send": {
const [body] = args;
this.request.addEventListener("load", () => {
if (typeof this.onResponse !== "undefined") {
const fetchResponse = createResponse(
this.request,
/**
* The `response` property is the right way to read
* the ambiguous response body, as the request's "responseType" may differ.
* @see https://xhr.spec.whatwg.org/#the-response-attribute
*/
this.request.response
);
this.onResponse.call(this, {
response: fetchResponse,
isMockedResponse: this[kIsRequestHandled],
request: fetchRequest,
requestId: this.requestId
});
}
});
const requestBody = typeof body === "string" ? require_bufferUtils.encodeBuffer(body) : body;
const fetchRequest = this.toFetchApiRequest(requestBody);
this[kFetchRequest] = fetchRequest.clone();
/**
* @note Start request handling on the next tick so that the user
* could add event listeners for "loadend" before the interceptor fires it.
*/
queueMicrotask(() => {
(this.onRequest?.call(this, {
request: fetchRequest,
requestId: this.requestId
}) || Promise.resolve()).finally(() => {
if (!this[kIsRequestHandled]) {
this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState);
/**
* @note Set the intercepted request ID on the original request in Node.js
* so that if it triggers any other interceptors, they don't attempt
* to process it once again.
*
* For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
* and we don't want for both XHR and ClientRequest interceptors to
* handle the same request at the same time (e.g. emit the "response" event twice).
*/
if (IS_NODE) this.request.setRequestHeader(require_createRequestId.INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId);
return invoke();
}
});
});
break;
}
default: return invoke();
}
}
});
/**
* Proxy the `.upload` property to gather the event listeners/callbacks.
*/
define(this.request, "upload", createProxy(this.request.upload, {
setProperty: ([propertyName, nextValue], invoke) => {
switch (propertyName) {
case "onloadstart":
case "onprogress":
case "onaboart":
case "onerror":
case "onload":
case "ontimeout":
case "onloadend": {
const eventName = propertyName.slice(2);
this.registerUploadEvent(eventName, nextValue);
}
}
return invoke();
},
methodCall: ([methodName, args], invoke) => {
switch (methodName) {
case "addEventListener": {
const [eventName, listener] = args;
this.registerUploadEvent(eventName, listener);
this.logger.info("upload.addEventListener", eventName, listener);
return invoke();
}
}
}
}));
}
registerEvent(eventName, listener) {
const nextEvents = (this.events.get(eventName) || []).concat(listener);
this.events.set(eventName, nextEvents);
this.logger.info("registered event \"%s\"", eventName, listener);
}
registerUploadEvent(eventName, listener) {
const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener);
this.uploadEvents.set(eventName, nextEvents);
this.logger.info("registered upload event \"%s\"", eventName, listener);
}
/**
* Responds to the current request with the given
* Fetch API `Response` instance.
*/
async respondWith(response) {
/**
* @note Since `XMLHttpRequestController` delegates the handling of the responses
* to the "load" event listener that doesn't distinguish between the mocked and original
* responses, mark the request that had a mocked response with a corresponding symbol.
*
* Mark this request as having a mocked response immediately since
* calculating request/response total body length is asynchronous.
*/
this[kIsRequestHandled] = true;
/**
* Dispatch request upload events for requests with a body.
* @see https://github.com/mswjs/interceptors/issues/573
*/
if (this[kFetchRequest]) {
const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]);
this.trigger("loadstart", this.request.upload, {
loaded: 0,
total: totalRequestBodyLength
});
this.trigger("progress", this.request.upload, {
loaded: totalRequestBodyLength,
total: totalRequestBodyLength
});
this.trigger("load", this.request.upload, {
loaded: totalRequestBodyLength,
total: totalRequestBodyLength
});
this.trigger("loadend", this.request.upload, {
loaded: totalRequestBodyLength,
total: totalRequestBodyLength
});
}
this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText);
define(this.request, "status", response.status);
define(this.request, "statusText", response.statusText);
define(this.request, "responseURL", this.url.href);
this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => {
this.logger.info("getResponseHeader", args[0]);
if (this.request.readyState < this.request.HEADERS_RECEIVED) {
this.logger.info("headers not received yet, returning null");
return null;
}
const headerValue = response.headers.get(args[0]);
this.logger.info("resolved response header \"%s\" to", args[0], headerValue);
return headerValue;
} });
this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => {
this.logger.info("getAllResponseHeaders");
if (this.request.readyState < this.request.HEADERS_RECEIVED) {
this.logger.info("headers not received yet, returning empty string");
return "";
}
const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => {
return `${headerName}: ${headerValue}`;
}).join("\r\n");
this.logger.info("resolved all response headers to", allHeaders);
return allHeaders;
} });
Object.defineProperties(this.request, {
response: {
enumerable: true,
configurable: false,
get: () => this.response
},
responseText: {
enumerable: true,
configurable: false,
get: () => this.responseText
},
responseXML: {
enumerable: true,
configurable: false,
get: () => this.responseXML
}
});
const totalResponseBodyLength = await getBodyByteLength(response.clone());
this.logger.info("calculated response body length", totalResponseBodyLength);
this.trigger("loadstart", this.request, {
loaded: 0,
total: totalResponseBodyLength
});
this.setReadyState(this.request.HEADERS_RECEIVED);
this.setReadyState(this.request.LOADING);
const finalizeResponse = () => {
this.logger.info("finalizing the mocked response...");
this.setReadyState(this.request.DONE);
this.trigger("load", this.request, {
loaded: this.responseBuffer.byteLength,
total: totalResponseBodyLength
});
this.trigger("loadend", this.request, {
loaded: this.responseBuffer.byteLength,
total: totalResponseBodyLength
});
};
if (response.body) {
this.logger.info("mocked response has body, streaming...");
const reader = response.body.getReader();
const readNextResponseBodyChunk = async () => {
const { value, done } = await reader.read();
if (done) {
this.logger.info("response body stream done!");
finalizeResponse();
return;
}
if (value) {
this.logger.info("read response body chunk:", value);
this.responseBuffer = concatArrayBuffer(this.responseBuffer, value);
this.trigger("progress", this.request, {
loaded: this.responseBuffer.byteLength,
total: totalResponseBodyLength
});
}
readNextResponseBodyChunk();
};
readNextResponseBodyChunk();
} else finalizeResponse();
}
responseBufferToText() {
return require_bufferUtils.decodeBuffer(this.responseBuffer);
}
get response() {
this.logger.info("getResponse (responseType: %s)", this.request.responseType);
if (this.request.readyState !== this.request.DONE) return null;
switch (this.request.responseType) {
case "json": {
const responseJson = parseJson(this.responseBufferToText());
this.logger.info("resolved response JSON", responseJson);
return responseJson;
}
case "arraybuffer": {
const arrayBuffer = require_bufferUtils.toArrayBuffer(this.responseBuffer);
this.logger.info("resolved response ArrayBuffer", arrayBuffer);
return arrayBuffer;
}
case "blob": {
const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain";
const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType });
this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType);
return responseBlob;
}
default: {
const responseText = this.responseBufferToText();
this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText);
return responseText;
}
}
}
get responseText() {
/**
* Throw when trying to read the response body as text when the
* "responseType" doesn't expect text. This just respects the spec better.
* @see https://xhr.spec.whatwg.org/#the-responsetext-attribute
*/
(0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state.");
if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return "";
const responseText = this.responseBufferToText();
this.logger.info("getResponseText: \"%s\"", responseText);
return responseText;
}
get responseXML() {
(0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state.");
if (this.request.readyState !== this.request.DONE) return null;
const contentType = this.request.getResponseHeader("Content-Type") || "";
if (typeof DOMParser === "undefined") {
console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.");
return null;
}
if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType);
return null;
}
errorWith(error) {
/**
* @note Mark this request as handled even if it received a mock error.
* This prevents the controller from trying to perform this request as-is.
*/
this[kIsRequestHandled] = true;
this.logger.info("responding with an error");
this.setReadyState(this.request.DONE);
this.trigger("error", this.request);
this.trigger("loadend", this.request);
}
/**
* Transitions this request's `readyState` to the given one.
*/
setReadyState(nextReadyState) {
this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState);
if (this.request.readyState === nextReadyState) {
this.logger.info("ready state identical, skipping transition...");
return;
}
define(this.request, "readyState", nextReadyState);
this.logger.info("set readyState to: %d", nextReadyState);
if (nextReadyState !== this.request.UNSENT) {
this.logger.info("triggering \"readystatechange\" event...");
this.trigger("readystatechange", this.request);
}
}
/**
* Triggers given event on the `XMLHttpRequest` instance.
*/
trigger(eventName, target, options) {
const callback = target[`on${eventName}`];
const event = createEvent(target, eventName, options);
this.logger.info("trigger \"%s\"", eventName, options || "");
if (typeof callback === "function") {
this.logger.info("found a direct \"%s\" callback, calling...", eventName);
callback.call(target, event);
}
const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events;
for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) {
this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName);
listeners.forEach((listener) => listener.call(target, event));
}
}
/**
* Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.
*/
toFetchApiRequest(body) {
this.logger.info("converting request to a Fetch API Request...");
const resolvedBody = body instanceof Document ? body.documentElement.innerText : body;
const fetchRequest = new Request(this.url.href, {
method: this.method,
headers: this.requestHeaders,
credentials: this.request.withCredentials ? "include" : "same-origin",
body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody
});
define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => {
switch (methodName) {
case "append":
case "set": {
const [headerName, headerValue] = args;
this.request.setRequestHeader(headerName, headerValue);
break;
}
case "delete": {
const [headerName] = args;
console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`);
break;
}
}
return invoke();
} }));
require_getRawRequest.setRawRequest(fetchRequest, this.request);
this.logger.info("converted request to a Fetch API Request!", fetchRequest);
return fetchRequest;
}
};
function toAbsoluteUrl(url) {
/**
* @note XMLHttpRequest interceptor may run in environments
* that implement XMLHttpRequest but don't implement "location"
* (for example, React Native). If that's the case, return the
* input URL as-is (nothing to be relative to).
* @see https://github.com/mswjs/msw/issues/1777
*/
if (typeof location === "undefined") return new URL(url);
return new URL(url.toString(), location.href);
}
function define(target, property, value) {
Reflect.defineProperty(target, property, {
writable: true,
enumerable: true,
value
});
}
//#endregion
//#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts
/**
* Create a proxied `XMLHttpRequest` class.
* The proxied class establishes spies on certain methods,
* allowing us to intercept requests and respond to them.
*/
function createXMLHttpRequestProxy({ emitter, logger }) {
return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) {
logger.info("constructed new XMLHttpRequest");
const originalRequest = Reflect.construct(target, args, newTarget);
/**
* @note Forward prototype descriptors onto the proxied object.
* XMLHttpRequest is implemented in JSDOM in a way that assigns
* a bunch of descriptors, like "set responseType()" on the prototype.
* With this propagation, we make sure that those descriptors trigger
* when the user operates with the proxied request instance.
*/
const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype);
for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]);
const xhrRequestController = new XMLHttpRequestController(originalRequest, logger);
xhrRequestController.onRequest = async function({ request, requestId }) {
const controller = new require_getRawRequest.RequestController(request, {
passthrough: () => {
this.logger.info("no mocked response received, performing request as-is...");
},
respondWith: async (response) => {
if (require_handleRequest.isResponseError(response)) {
this.errorWith(/* @__PURE__ */ new TypeError("Network error"));
return;
}
await this.respondWith(response);
},
errorWith: (reason) => {
this.logger.info("request errored!", { error: reason });
if (reason instanceof Error) this.errorWith(reason);
}
});
this.logger.info("awaiting mocked response...");
this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request"));
await require_handleRequest.handleRequest({
request,
requestId,
controller,
emitter
});
};
xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) {
this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response"));
emitter.emit("response", {
response,
isMockedResponse,
request,
requestId
});
};
return xhrRequestController.request;
} });
}
//#endregion
//#region src/interceptors/XMLHttpRequest/index.ts
var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends require_createRequestId.Interceptor {
static {
this.interceptorSymbol = Symbol("xhr");
}
constructor() {
super(XMLHttpRequestInterceptor.interceptorSymbol);
}
checkEnvironment() {
return require_hasConfigurableGlobal.hasConfigurableGlobal("XMLHttpRequest");
}
setup() {
const logger = this.logger.extend("setup");
logger.info("patching \"XMLHttpRequest\" module...");
const PureXMLHttpRequest = globalThis.XMLHttpRequest;
(0, outvariant.invariant)(!PureXMLHttpRequest[require_getRawRequest.IS_PATCHED_MODULE], "Failed to patch the \"XMLHttpRequest\" module: already patched.");
globalThis.XMLHttpRequest = createXMLHttpRequestProxy({
emitter: this.emitter,
logger: this.logger
});
logger.info("native \"XMLHttpRequest\" module patched!", globalThis.XMLHttpRequest.name);
Object.defineProperty(globalThis.XMLHttpRequest, require_getRawRequest.IS_PATCHED_MODULE, {
enumerable: true,
configurable: true,
value: true
});
this.subscriptions.push(() => {
Object.defineProperty(globalThis.XMLHttpRequest, require_getRawRequest.IS_PATCHED_MODULE, { value: void 0 });
globalThis.XMLHttpRequest = PureXMLHttpRequest;
logger.info("native \"XMLHttpRequest\" module restored!", globalThis.XMLHttpRequest.name);
});
}
};
//#endregion
Object.defineProperty(exports, 'XMLHttpRequestInterceptor', {
enumerable: true,
get: function () {
return XMLHttpRequestInterceptor;
}
});
//# sourceMappingURL=XMLHttpRequest-BACqefB-.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,756 @@
import { a as RequestController, n as setRawRequest, r as FetchResponse, s as IS_PATCHED_MODULE } from "./getRawRequest-BTaNLFr0.mjs";
import { n as INTERNAL_REQUEST_ID_HEADER_NAME, r as Interceptor, t as createRequestId } from "./createRequestId-DQcIlohW.mjs";
import { n as encodeBuffer, r as toArrayBuffer, t as decodeBuffer } from "./bufferUtils-BiiO6HZv.mjs";
import { t as hasConfigurableGlobal } from "./hasConfigurableGlobal-npXitu1-.mjs";
import { n as isResponseError, t as handleRequest } from "./handleRequest-D7kpTI5U.mjs";
import { invariant } from "outvariant";
import { isNodeProcess } from "is-node-process";
//#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts
/**
* Concatenate two `Uint8Array` buffers.
*/
function concatArrayBuffer(left, right) {
const result = new Uint8Array(left.byteLength + right.byteLength);
result.set(left, 0);
result.set(right, left.byteLength);
return result;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts
var EventPolyfill = class {
constructor(type, options) {
this.NONE = 0;
this.CAPTURING_PHASE = 1;
this.AT_TARGET = 2;
this.BUBBLING_PHASE = 3;
this.type = "";
this.srcElement = null;
this.currentTarget = null;
this.eventPhase = 0;
this.isTrusted = true;
this.composed = false;
this.cancelable = true;
this.defaultPrevented = false;
this.bubbles = true;
this.lengthComputable = true;
this.loaded = 0;
this.total = 0;
this.cancelBubble = false;
this.returnValue = true;
this.type = type;
this.target = options?.target || null;
this.currentTarget = options?.currentTarget || null;
this.timeStamp = Date.now();
}
composedPath() {
return [];
}
initEvent(type, bubbles, cancelable) {
this.type = type;
this.bubbles = !!bubbles;
this.cancelable = !!cancelable;
}
preventDefault() {
this.defaultPrevented = true;
}
stopPropagation() {}
stopImmediatePropagation() {}
};
//#endregion
//#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts
var ProgressEventPolyfill = class extends EventPolyfill {
constructor(type, init) {
super(type);
this.lengthComputable = init?.lengthComputable || false;
this.composed = init?.composed || false;
this.loaded = init?.loaded || 0;
this.total = init?.total || 0;
}
};
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/createEvent.ts
const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined";
function createEvent(target, type, init) {
const progressEvents = [
"error",
"progress",
"loadstart",
"loadend",
"load",
"timeout",
"abort"
];
/**
* `ProgressEvent` is not supported in React Native.
* @see https://github.com/mswjs/interceptors/issues/40
*/
const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill;
return progressEvents.includes(type) ? new ProgressEventClass(type, {
lengthComputable: true,
loaded: init?.loaded || 0,
total: init?.total || 0
}) : new EventPolyfill(type, {
target,
currentTarget: target
});
}
//#endregion
//#region src/utils/findPropertySource.ts
/**
* Returns the source object of the given property on the target object
* (the target itself, any parent in its prototype, or null).
*/
function findPropertySource(target, propertyName) {
if (!(propertyName in target)) return null;
if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target;
const prototype = Reflect.getPrototypeOf(target);
return prototype ? findPropertySource(prototype, propertyName) : null;
}
//#endregion
//#region src/utils/createProxy.ts
function createProxy(target, options) {
return new Proxy(target, optionsToProxyHandler(options));
}
function optionsToProxyHandler(options) {
const { constructorCall, methodCall, getProperty, setProperty } = options;
const handler = {};
if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) {
const next = Reflect.construct.bind(null, target, args, newTarget);
return constructorCall.call(newTarget, args, next);
};
handler.set = function(target, propertyName, nextValue) {
const next = () => {
const propertySource = findPropertySource(target, propertyName) || target;
const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName);
if (typeof ownDescriptors?.set !== "undefined") {
ownDescriptors.set.apply(target, [nextValue]);
return true;
}
return Reflect.defineProperty(propertySource, propertyName, {
writable: true,
enumerable: true,
configurable: true,
value: nextValue
});
};
if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next);
return next();
};
handler.get = function(target, propertyName, receiver) {
/**
* @note Using `Reflect.get()` here causes "TypeError: Illegal invocation".
*/
const next = () => target[propertyName];
const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next();
if (typeof value === "function") return (...args) => {
const next$1 = value.bind(target, ...args);
if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1);
return next$1();
};
return value;
};
return handler;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts
function isDomParserSupportedType(type) {
return [
"application/xhtml+xml",
"application/xml",
"image/svg+xml",
"text/html",
"text/xml"
].some((supportedType) => {
return type.startsWith(supportedType);
});
}
//#endregion
//#region src/utils/parseJson.ts
/**
* Parses a given string into JSON.
* Gracefully handles invalid JSON by returning `null`.
*/
function parseJson(data) {
try {
return JSON.parse(data);
} catch (_) {
return null;
}
}
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/createResponse.ts
/**
* Creates a Fetch API `Response` instance from the given
* `XMLHttpRequest` instance and a response body.
*/
function createResponse(request, body) {
return new FetchResponse(FetchResponse.isResponseWithBody(request.status) ? body : null, {
url: request.responseURL,
status: request.status,
statusText: request.statusText,
headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders())
});
}
function createHeadersFromXMLHttpRequestHeaders(headersString) {
const headers = new Headers();
const lines = headersString.split(/[\r\n]+/);
for (const line of lines) {
if (line.trim() === "") continue;
const [name, ...parts] = line.split(": ");
const value = parts.join(": ");
headers.append(name, value);
}
return headers;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts
/**
* Return a total byte length of the given request/response body.
* If the `Content-Length` header is present, it will be used as the byte length.
*/
async function getBodyByteLength(input) {
const explicitContentLength = input.headers.get("content-length");
if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength);
return (await input.arrayBuffer()).byteLength;
}
//#endregion
//#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts
const kIsRequestHandled = Symbol("kIsRequestHandled");
const IS_NODE = isNodeProcess();
const kFetchRequest = Symbol("kFetchRequest");
/**
* An `XMLHttpRequest` instance controller that allows us
* to handle any given request instance (e.g. responding to it).
*/
var XMLHttpRequestController = class {
constructor(initialRequest, logger) {
this.initialRequest = initialRequest;
this.logger = logger;
this.method = "GET";
this.url = null;
this[kIsRequestHandled] = false;
this.events = /* @__PURE__ */ new Map();
this.uploadEvents = /* @__PURE__ */ new Map();
this.requestId = createRequestId();
this.requestHeaders = new Headers();
this.responseBuffer = new Uint8Array();
this.request = createProxy(initialRequest, {
setProperty: ([propertyName, nextValue], invoke) => {
switch (propertyName) {
case "ontimeout": {
const eventName = propertyName.slice(2);
/**
* @note Proxy callbacks to event listeners because JSDOM has trouble
* translating these properties to callbacks. It seemed to be operating
* on events exclusively.
*/
this.request.addEventListener(eventName, nextValue);
return invoke();
}
default: return invoke();
}
},
methodCall: ([methodName, args], invoke) => {
switch (methodName) {
case "open": {
const [method, url] = args;
if (typeof url === "undefined") {
this.method = "GET";
this.url = toAbsoluteUrl(method);
} else {
this.method = method;
this.url = toAbsoluteUrl(url);
}
this.logger = this.logger.extend(`${this.method} ${this.url.href}`);
this.logger.info("open", this.method, this.url.href);
return invoke();
}
case "addEventListener": {
const [eventName, listener] = args;
this.registerEvent(eventName, listener);
this.logger.info("addEventListener", eventName, listener);
return invoke();
}
case "setRequestHeader": {
const [name, value] = args;
this.requestHeaders.set(name, value);
this.logger.info("setRequestHeader", name, value);
return invoke();
}
case "send": {
const [body] = args;
this.request.addEventListener("load", () => {
if (typeof this.onResponse !== "undefined") {
const fetchResponse = createResponse(
this.request,
/**
* The `response` property is the right way to read
* the ambiguous response body, as the request's "responseType" may differ.
* @see https://xhr.spec.whatwg.org/#the-response-attribute
*/
this.request.response
);
this.onResponse.call(this, {
response: fetchResponse,
isMockedResponse: this[kIsRequestHandled],
request: fetchRequest,
requestId: this.requestId
});
}
});
const requestBody = typeof body === "string" ? encodeBuffer(body) : body;
const fetchRequest = this.toFetchApiRequest(requestBody);
this[kFetchRequest] = fetchRequest.clone();
/**
* @note Start request handling on the next tick so that the user
* could add event listeners for "loadend" before the interceptor fires it.
*/
queueMicrotask(() => {
(this.onRequest?.call(this, {
request: fetchRequest,
requestId: this.requestId
}) || Promise.resolve()).finally(() => {
if (!this[kIsRequestHandled]) {
this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState);
/**
* @note Set the intercepted request ID on the original request in Node.js
* so that if it triggers any other interceptors, they don't attempt
* to process it once again.
*
* For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
* and we don't want for both XHR and ClientRequest interceptors to
* handle the same request at the same time (e.g. emit the "response" event twice).
*/
if (IS_NODE) this.request.setRequestHeader(INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId);
return invoke();
}
});
});
break;
}
default: return invoke();
}
}
});
/**
* Proxy the `.upload` property to gather the event listeners/callbacks.
*/
define(this.request, "upload", createProxy(this.request.upload, {
setProperty: ([propertyName, nextValue], invoke) => {
switch (propertyName) {
case "onloadstart":
case "onprogress":
case "onaboart":
case "onerror":
case "onload":
case "ontimeout":
case "onloadend": {
const eventName = propertyName.slice(2);
this.registerUploadEvent(eventName, nextValue);
}
}
return invoke();
},
methodCall: ([methodName, args], invoke) => {
switch (methodName) {
case "addEventListener": {
const [eventName, listener] = args;
this.registerUploadEvent(eventName, listener);
this.logger.info("upload.addEventListener", eventName, listener);
return invoke();
}
}
}
}));
}
registerEvent(eventName, listener) {
const nextEvents = (this.events.get(eventName) || []).concat(listener);
this.events.set(eventName, nextEvents);
this.logger.info("registered event \"%s\"", eventName, listener);
}
registerUploadEvent(eventName, listener) {
const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener);
this.uploadEvents.set(eventName, nextEvents);
this.logger.info("registered upload event \"%s\"", eventName, listener);
}
/**
* Responds to the current request with the given
* Fetch API `Response` instance.
*/
async respondWith(response) {
/**
* @note Since `XMLHttpRequestController` delegates the handling of the responses
* to the "load" event listener that doesn't distinguish between the mocked and original
* responses, mark the request that had a mocked response with a corresponding symbol.
*
* Mark this request as having a mocked response immediately since
* calculating request/response total body length is asynchronous.
*/
this[kIsRequestHandled] = true;
/**
* Dispatch request upload events for requests with a body.
* @see https://github.com/mswjs/interceptors/issues/573
*/
if (this[kFetchRequest]) {
const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]);
this.trigger("loadstart", this.request.upload, {
loaded: 0,
total: totalRequestBodyLength
});
this.trigger("progress", this.request.upload, {
loaded: totalRequestBodyLength,
total: totalRequestBodyLength
});
this.trigger("load", this.request.upload, {
loaded: totalRequestBodyLength,
total: totalRequestBodyLength
});
this.trigger("loadend", this.request.upload, {
loaded: totalRequestBodyLength,
total: totalRequestBodyLength
});
}
this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText);
define(this.request, "status", response.status);
define(this.request, "statusText", response.statusText);
define(this.request, "responseURL", this.url.href);
this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => {
this.logger.info("getResponseHeader", args[0]);
if (this.request.readyState < this.request.HEADERS_RECEIVED) {
this.logger.info("headers not received yet, returning null");
return null;
}
const headerValue = response.headers.get(args[0]);
this.logger.info("resolved response header \"%s\" to", args[0], headerValue);
return headerValue;
} });
this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => {
this.logger.info("getAllResponseHeaders");
if (this.request.readyState < this.request.HEADERS_RECEIVED) {
this.logger.info("headers not received yet, returning empty string");
return "";
}
const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => {
return `${headerName}: ${headerValue}`;
}).join("\r\n");
this.logger.info("resolved all response headers to", allHeaders);
return allHeaders;
} });
Object.defineProperties(this.request, {
response: {
enumerable: true,
configurable: false,
get: () => this.response
},
responseText: {
enumerable: true,
configurable: false,
get: () => this.responseText
},
responseXML: {
enumerable: true,
configurable: false,
get: () => this.responseXML
}
});
const totalResponseBodyLength = await getBodyByteLength(response.clone());
this.logger.info("calculated response body length", totalResponseBodyLength);
this.trigger("loadstart", this.request, {
loaded: 0,
total: totalResponseBodyLength
});
this.setReadyState(this.request.HEADERS_RECEIVED);
this.setReadyState(this.request.LOADING);
const finalizeResponse = () => {
this.logger.info("finalizing the mocked response...");
this.setReadyState(this.request.DONE);
this.trigger("load", this.request, {
loaded: this.responseBuffer.byteLength,
total: totalResponseBodyLength
});
this.trigger("loadend", this.request, {
loaded: this.responseBuffer.byteLength,
total: totalResponseBodyLength
});
};
if (response.body) {
this.logger.info("mocked response has body, streaming...");
const reader = response.body.getReader();
const readNextResponseBodyChunk = async () => {
const { value, done } = await reader.read();
if (done) {
this.logger.info("response body stream done!");
finalizeResponse();
return;
}
if (value) {
this.logger.info("read response body chunk:", value);
this.responseBuffer = concatArrayBuffer(this.responseBuffer, value);
this.trigger("progress", this.request, {
loaded: this.responseBuffer.byteLength,
total: totalResponseBodyLength
});
}
readNextResponseBodyChunk();
};
readNextResponseBodyChunk();
} else finalizeResponse();
}
responseBufferToText() {
return decodeBuffer(this.responseBuffer);
}
get response() {
this.logger.info("getResponse (responseType: %s)", this.request.responseType);
if (this.request.readyState !== this.request.DONE) return null;
switch (this.request.responseType) {
case "json": {
const responseJson = parseJson(this.responseBufferToText());
this.logger.info("resolved response JSON", responseJson);
return responseJson;
}
case "arraybuffer": {
const arrayBuffer = toArrayBuffer(this.responseBuffer);
this.logger.info("resolved response ArrayBuffer", arrayBuffer);
return arrayBuffer;
}
case "blob": {
const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain";
const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType });
this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType);
return responseBlob;
}
default: {
const responseText = this.responseBufferToText();
this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText);
return responseText;
}
}
}
get responseText() {
/**
* Throw when trying to read the response body as text when the
* "responseType" doesn't expect text. This just respects the spec better.
* @see https://xhr.spec.whatwg.org/#the-responsetext-attribute
*/
invariant(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state.");
if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return "";
const responseText = this.responseBufferToText();
this.logger.info("getResponseText: \"%s\"", responseText);
return responseText;
}
get responseXML() {
invariant(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state.");
if (this.request.readyState !== this.request.DONE) return null;
const contentType = this.request.getResponseHeader("Content-Type") || "";
if (typeof DOMParser === "undefined") {
console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.");
return null;
}
if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType);
return null;
}
errorWith(error) {
/**
* @note Mark this request as handled even if it received a mock error.
* This prevents the controller from trying to perform this request as-is.
*/
this[kIsRequestHandled] = true;
this.logger.info("responding with an error");
this.setReadyState(this.request.DONE);
this.trigger("error", this.request);
this.trigger("loadend", this.request);
}
/**
* Transitions this request's `readyState` to the given one.
*/
setReadyState(nextReadyState) {
this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState);
if (this.request.readyState === nextReadyState) {
this.logger.info("ready state identical, skipping transition...");
return;
}
define(this.request, "readyState", nextReadyState);
this.logger.info("set readyState to: %d", nextReadyState);
if (nextReadyState !== this.request.UNSENT) {
this.logger.info("triggering \"readystatechange\" event...");
this.trigger("readystatechange", this.request);
}
}
/**
* Triggers given event on the `XMLHttpRequest` instance.
*/
trigger(eventName, target, options) {
const callback = target[`on${eventName}`];
const event = createEvent(target, eventName, options);
this.logger.info("trigger \"%s\"", eventName, options || "");
if (typeof callback === "function") {
this.logger.info("found a direct \"%s\" callback, calling...", eventName);
callback.call(target, event);
}
const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events;
for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) {
this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName);
listeners.forEach((listener) => listener.call(target, event));
}
}
/**
* Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.
*/
toFetchApiRequest(body) {
this.logger.info("converting request to a Fetch API Request...");
const resolvedBody = body instanceof Document ? body.documentElement.innerText : body;
const fetchRequest = new Request(this.url.href, {
method: this.method,
headers: this.requestHeaders,
credentials: this.request.withCredentials ? "include" : "same-origin",
body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody
});
define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => {
switch (methodName) {
case "append":
case "set": {
const [headerName, headerValue] = args;
this.request.setRequestHeader(headerName, headerValue);
break;
}
case "delete": {
const [headerName] = args;
console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`);
break;
}
}
return invoke();
} }));
setRawRequest(fetchRequest, this.request);
this.logger.info("converted request to a Fetch API Request!", fetchRequest);
return fetchRequest;
}
};
function toAbsoluteUrl(url) {
/**
* @note XMLHttpRequest interceptor may run in environments
* that implement XMLHttpRequest but don't implement "location"
* (for example, React Native). If that's the case, return the
* input URL as-is (nothing to be relative to).
* @see https://github.com/mswjs/msw/issues/1777
*/
if (typeof location === "undefined") return new URL(url);
return new URL(url.toString(), location.href);
}
function define(target, property, value) {
Reflect.defineProperty(target, property, {
writable: true,
enumerable: true,
value
});
}
//#endregion
//#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts
/**
* Create a proxied `XMLHttpRequest` class.
* The proxied class establishes spies on certain methods,
* allowing us to intercept requests and respond to them.
*/
function createXMLHttpRequestProxy({ emitter, logger }) {
return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) {
logger.info("constructed new XMLHttpRequest");
const originalRequest = Reflect.construct(target, args, newTarget);
/**
* @note Forward prototype descriptors onto the proxied object.
* XMLHttpRequest is implemented in JSDOM in a way that assigns
* a bunch of descriptors, like "set responseType()" on the prototype.
* With this propagation, we make sure that those descriptors trigger
* when the user operates with the proxied request instance.
*/
const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype);
for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]);
const xhrRequestController = new XMLHttpRequestController(originalRequest, logger);
xhrRequestController.onRequest = async function({ request, requestId }) {
const controller = new RequestController(request, {
passthrough: () => {
this.logger.info("no mocked response received, performing request as-is...");
},
respondWith: async (response) => {
if (isResponseError(response)) {
this.errorWith(/* @__PURE__ */ new TypeError("Network error"));
return;
}
await this.respondWith(response);
},
errorWith: (reason) => {
this.logger.info("request errored!", { error: reason });
if (reason instanceof Error) this.errorWith(reason);
}
});
this.logger.info("awaiting mocked response...");
this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request"));
await handleRequest({
request,
requestId,
controller,
emitter
});
};
xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) {
this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response"));
emitter.emit("response", {
response,
isMockedResponse,
request,
requestId
});
};
return xhrRequestController.request;
} });
}
//#endregion
//#region src/interceptors/XMLHttpRequest/index.ts
var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends Interceptor {
static {
this.interceptorSymbol = Symbol("xhr");
}
constructor() {
super(XMLHttpRequestInterceptor.interceptorSymbol);
}
checkEnvironment() {
return hasConfigurableGlobal("XMLHttpRequest");
}
setup() {
const logger = this.logger.extend("setup");
logger.info("patching \"XMLHttpRequest\" module...");
const PureXMLHttpRequest = globalThis.XMLHttpRequest;
invariant(!PureXMLHttpRequest[IS_PATCHED_MODULE], "Failed to patch the \"XMLHttpRequest\" module: already patched.");
globalThis.XMLHttpRequest = createXMLHttpRequestProxy({
emitter: this.emitter,
logger: this.logger
});
logger.info("native \"XMLHttpRequest\" module patched!", globalThis.XMLHttpRequest.name);
Object.defineProperty(globalThis.XMLHttpRequest, IS_PATCHED_MODULE, {
enumerable: true,
configurable: true,
value: true
});
this.subscriptions.push(() => {
Object.defineProperty(globalThis.XMLHttpRequest, IS_PATCHED_MODULE, { value: void 0 });
globalThis.XMLHttpRequest = PureXMLHttpRequest;
logger.info("native \"XMLHttpRequest\" module restored!", globalThis.XMLHttpRequest.name);
});
}
};
//#endregion
export { XMLHttpRequestInterceptor as t };
//# sourceMappingURL=XMLHttpRequest-BvxZV0WU.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
//#region src/utils/bufferUtils.ts
const encoder = new TextEncoder();
function encodeBuffer(text) {
return encoder.encode(text);
}
function decodeBuffer(buffer, encoding) {
return new TextDecoder(encoding).decode(buffer);
}
/**
* Create an `ArrayBuffer` from the given `Uint8Array`.
* Takes the byte offset into account to produce the right buffer
* in the case when the buffer is bigger than the data view.
*/
function toArrayBuffer(array) {
return array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength);
}
//#endregion
export { encodeBuffer as n, toArrayBuffer as r, decodeBuffer as t };
//# sourceMappingURL=bufferUtils-BiiO6HZv.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bufferUtils-BiiO6HZv.mjs","names":[],"sources":["../../src/utils/bufferUtils.ts"],"sourcesContent":["const encoder = new TextEncoder()\n\nexport function encodeBuffer(text: string): Uint8Array {\n return encoder.encode(text)\n}\n\nexport function decodeBuffer(buffer: ArrayBuffer, encoding?: string): string {\n const decoder = new TextDecoder(encoding)\n return decoder.decode(buffer)\n}\n\n/**\n * Create an `ArrayBuffer` from the given `Uint8Array`.\n * Takes the byte offset into account to produce the right buffer\n * in the case when the buffer is bigger than the data view.\n */\nexport function toArrayBuffer(array: Uint8Array): ArrayBuffer {\n return array.buffer.slice(\n array.byteOffset,\n array.byteOffset + array.byteLength\n )\n}\n"],"mappings":";AAAA,MAAM,UAAU,IAAI,aAAa;AAEjC,SAAgB,aAAa,MAA0B;AACrD,QAAO,QAAQ,OAAO,KAAK;;AAG7B,SAAgB,aAAa,QAAqB,UAA2B;AAE3E,QADgB,IAAI,YAAY,SAAS,CAC1B,OAAO,OAAO;;;;;;;AAQ/B,SAAgB,cAAc,OAAgC;AAC5D,QAAO,MAAM,OAAO,MAClB,MAAM,YACN,MAAM,aAAa,MAAM,WAC1B"}

View File

@@ -0,0 +1,38 @@
//#region src/utils/bufferUtils.ts
const encoder = new TextEncoder();
function encodeBuffer(text) {
return encoder.encode(text);
}
function decodeBuffer(buffer, encoding) {
return new TextDecoder(encoding).decode(buffer);
}
/**
* Create an `ArrayBuffer` from the given `Uint8Array`.
* Takes the byte offset into account to produce the right buffer
* in the case when the buffer is bigger than the data view.
*/
function toArrayBuffer(array) {
return array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength);
}
//#endregion
Object.defineProperty(exports, 'decodeBuffer', {
enumerable: true,
get: function () {
return decodeBuffer;
}
});
Object.defineProperty(exports, 'encodeBuffer', {
enumerable: true,
get: function () {
return encodeBuffer;
}
});
Object.defineProperty(exports, 'toArrayBuffer', {
enumerable: true,
get: function () {
return toArrayBuffer;
}
});
//# sourceMappingURL=bufferUtils-Uc0eRItL.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bufferUtils-Uc0eRItL.cjs","names":[],"sources":["../../src/utils/bufferUtils.ts"],"sourcesContent":["const encoder = new TextEncoder()\n\nexport function encodeBuffer(text: string): Uint8Array {\n return encoder.encode(text)\n}\n\nexport function decodeBuffer(buffer: ArrayBuffer, encoding?: string): string {\n const decoder = new TextDecoder(encoding)\n return decoder.decode(buffer)\n}\n\n/**\n * Create an `ArrayBuffer` from the given `Uint8Array`.\n * Takes the byte offset into account to produce the right buffer\n * in the case when the buffer is bigger than the data view.\n */\nexport function toArrayBuffer(array: Uint8Array): ArrayBuffer {\n return array.buffer.slice(\n array.byteOffset,\n array.byteOffset + array.byteLength\n )\n}\n"],"mappings":";;AAAA,MAAM,UAAU,IAAI,aAAa;AAEjC,SAAgB,aAAa,MAA0B;AACrD,QAAO,QAAQ,OAAO,KAAK;;AAG7B,SAAgB,aAAa,QAAqB,UAA2B;AAE3E,QADgB,IAAI,YAAY,SAAS,CAC1B,OAAO,OAAO;;;;;;;AAQ/B,SAAgB,cAAc,OAAgC;AAC5D,QAAO,MAAM,OAAO,MAClB,MAAM,YACN,MAAM,aAAa,MAAM,WAC1B"}

View File

@@ -0,0 +1,205 @@
let _open_draft_logger = require("@open-draft/logger");
let strict_event_emitter = require("strict-event-emitter");
//#region src/Interceptor.ts
/**
* Request header name to detect when a single request
* is being handled by nested interceptors (XHR -> ClientRequest).
* Obscure by design to prevent collisions with user-defined headers.
* Ideally, come up with the Interceptor-level mechanism for this.
* @see https://github.com/mswjs/interceptors/issues/378
*/
const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id";
function getGlobalSymbol(symbol) {
return globalThis[symbol] || void 0;
}
function setGlobalSymbol(symbol, value) {
globalThis[symbol] = value;
}
function deleteGlobalSymbol(symbol) {
delete globalThis[symbol];
}
let InterceptorReadyState = /* @__PURE__ */ function(InterceptorReadyState$1) {
InterceptorReadyState$1["INACTIVE"] = "INACTIVE";
InterceptorReadyState$1["APPLYING"] = "APPLYING";
InterceptorReadyState$1["APPLIED"] = "APPLIED";
InterceptorReadyState$1["DISPOSING"] = "DISPOSING";
InterceptorReadyState$1["DISPOSED"] = "DISPOSED";
return InterceptorReadyState$1;
}({});
var Interceptor = class {
constructor(symbol) {
this.symbol = symbol;
this.readyState = InterceptorReadyState.INACTIVE;
this.emitter = new strict_event_emitter.Emitter();
this.subscriptions = [];
this.logger = new _open_draft_logger.Logger(symbol.description);
this.emitter.setMaxListeners(0);
this.logger.info("constructing the interceptor...");
}
/**
* Determine if this interceptor can be applied
* in the current environment.
*/
checkEnvironment() {
return true;
}
/**
* Apply this interceptor to the current process.
* Returns an already running interceptor instance if it's present.
*/
apply() {
const logger = this.logger.extend("apply");
logger.info("applying the interceptor...");
if (this.readyState === InterceptorReadyState.APPLIED) {
logger.info("intercepted already applied!");
return;
}
if (!this.checkEnvironment()) {
logger.info("the interceptor cannot be applied in this environment!");
return;
}
this.readyState = InterceptorReadyState.APPLYING;
const runningInstance = this.getInstance();
if (runningInstance) {
logger.info("found a running instance, reusing...");
this.on = (event, listener) => {
logger.info("proxying the \"%s\" listener", event);
runningInstance.emitter.addListener(event, listener);
this.subscriptions.push(() => {
runningInstance.emitter.removeListener(event, listener);
logger.info("removed proxied \"%s\" listener!", event);
});
return this;
};
this.readyState = InterceptorReadyState.APPLIED;
return;
}
logger.info("no running instance found, setting up a new instance...");
this.setup();
this.setInstance();
this.readyState = InterceptorReadyState.APPLIED;
}
/**
* Setup the module augments and stubs necessary for this interceptor.
* This method is not run if there's a running interceptor instance
* to prevent instantiating an interceptor multiple times.
*/
setup() {}
/**
* Listen to the interceptor's public events.
*/
on(event, listener) {
const logger = this.logger.extend("on");
if (this.readyState === InterceptorReadyState.DISPOSING || this.readyState === InterceptorReadyState.DISPOSED) {
logger.info("cannot listen to events, already disposed!");
return this;
}
logger.info("adding \"%s\" event listener:", event, listener);
this.emitter.on(event, listener);
return this;
}
once(event, listener) {
this.emitter.once(event, listener);
return this;
}
off(event, listener) {
this.emitter.off(event, listener);
return this;
}
removeAllListeners(event) {
this.emitter.removeAllListeners(event);
return this;
}
/**
* Disposes of any side-effects this interceptor has introduced.
*/
dispose() {
const logger = this.logger.extend("dispose");
if (this.readyState === InterceptorReadyState.DISPOSED) {
logger.info("cannot dispose, already disposed!");
return;
}
logger.info("disposing the interceptor...");
this.readyState = InterceptorReadyState.DISPOSING;
if (!this.getInstance()) {
logger.info("no interceptors running, skipping dispose...");
return;
}
this.clearInstance();
logger.info("global symbol deleted:", getGlobalSymbol(this.symbol));
if (this.subscriptions.length > 0) {
logger.info("disposing of %d subscriptions...", this.subscriptions.length);
for (const dispose of this.subscriptions) dispose();
this.subscriptions = [];
logger.info("disposed of all subscriptions!", this.subscriptions.length);
}
this.emitter.removeAllListeners();
logger.info("destroyed the listener!");
this.readyState = InterceptorReadyState.DISPOSED;
}
getInstance() {
const instance = getGlobalSymbol(this.symbol);
this.logger.info("retrieved global instance:", instance?.constructor?.name);
return instance;
}
setInstance() {
setGlobalSymbol(this.symbol, this);
this.logger.info("set global instance!", this.symbol.description);
}
clearInstance() {
deleteGlobalSymbol(this.symbol);
this.logger.info("cleared global instance!", this.symbol.description);
}
};
//#endregion
//#region src/createRequestId.ts
/**
* Generate a random ID string to represent a request.
* @example
* createRequestId()
* // "f774b6c9c600f"
*/
function createRequestId() {
return Math.random().toString(16).slice(2);
}
//#endregion
Object.defineProperty(exports, 'INTERNAL_REQUEST_ID_HEADER_NAME', {
enumerable: true,
get: function () {
return INTERNAL_REQUEST_ID_HEADER_NAME;
}
});
Object.defineProperty(exports, 'Interceptor', {
enumerable: true,
get: function () {
return Interceptor;
}
});
Object.defineProperty(exports, 'InterceptorReadyState', {
enumerable: true,
get: function () {
return InterceptorReadyState;
}
});
Object.defineProperty(exports, 'createRequestId', {
enumerable: true,
get: function () {
return createRequestId;
}
});
Object.defineProperty(exports, 'deleteGlobalSymbol', {
enumerable: true,
get: function () {
return deleteGlobalSymbol;
}
});
Object.defineProperty(exports, 'getGlobalSymbol', {
enumerable: true,
get: function () {
return getGlobalSymbol;
}
});
//# sourceMappingURL=createRequestId-Cs4oXfa1.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,170 @@
import { Logger } from "@open-draft/logger";
import { Emitter } from "strict-event-emitter";
//#region src/Interceptor.ts
/**
* Request header name to detect when a single request
* is being handled by nested interceptors (XHR -> ClientRequest).
* Obscure by design to prevent collisions with user-defined headers.
* Ideally, come up with the Interceptor-level mechanism for this.
* @see https://github.com/mswjs/interceptors/issues/378
*/
const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id";
function getGlobalSymbol(symbol) {
return globalThis[symbol] || void 0;
}
function setGlobalSymbol(symbol, value) {
globalThis[symbol] = value;
}
function deleteGlobalSymbol(symbol) {
delete globalThis[symbol];
}
let InterceptorReadyState = /* @__PURE__ */ function(InterceptorReadyState$1) {
InterceptorReadyState$1["INACTIVE"] = "INACTIVE";
InterceptorReadyState$1["APPLYING"] = "APPLYING";
InterceptorReadyState$1["APPLIED"] = "APPLIED";
InterceptorReadyState$1["DISPOSING"] = "DISPOSING";
InterceptorReadyState$1["DISPOSED"] = "DISPOSED";
return InterceptorReadyState$1;
}({});
var Interceptor = class {
constructor(symbol) {
this.symbol = symbol;
this.readyState = InterceptorReadyState.INACTIVE;
this.emitter = new Emitter();
this.subscriptions = [];
this.logger = new Logger(symbol.description);
this.emitter.setMaxListeners(0);
this.logger.info("constructing the interceptor...");
}
/**
* Determine if this interceptor can be applied
* in the current environment.
*/
checkEnvironment() {
return true;
}
/**
* Apply this interceptor to the current process.
* Returns an already running interceptor instance if it's present.
*/
apply() {
const logger = this.logger.extend("apply");
logger.info("applying the interceptor...");
if (this.readyState === InterceptorReadyState.APPLIED) {
logger.info("intercepted already applied!");
return;
}
if (!this.checkEnvironment()) {
logger.info("the interceptor cannot be applied in this environment!");
return;
}
this.readyState = InterceptorReadyState.APPLYING;
const runningInstance = this.getInstance();
if (runningInstance) {
logger.info("found a running instance, reusing...");
this.on = (event, listener) => {
logger.info("proxying the \"%s\" listener", event);
runningInstance.emitter.addListener(event, listener);
this.subscriptions.push(() => {
runningInstance.emitter.removeListener(event, listener);
logger.info("removed proxied \"%s\" listener!", event);
});
return this;
};
this.readyState = InterceptorReadyState.APPLIED;
return;
}
logger.info("no running instance found, setting up a new instance...");
this.setup();
this.setInstance();
this.readyState = InterceptorReadyState.APPLIED;
}
/**
* Setup the module augments and stubs necessary for this interceptor.
* This method is not run if there's a running interceptor instance
* to prevent instantiating an interceptor multiple times.
*/
setup() {}
/**
* Listen to the interceptor's public events.
*/
on(event, listener) {
const logger = this.logger.extend("on");
if (this.readyState === InterceptorReadyState.DISPOSING || this.readyState === InterceptorReadyState.DISPOSED) {
logger.info("cannot listen to events, already disposed!");
return this;
}
logger.info("adding \"%s\" event listener:", event, listener);
this.emitter.on(event, listener);
return this;
}
once(event, listener) {
this.emitter.once(event, listener);
return this;
}
off(event, listener) {
this.emitter.off(event, listener);
return this;
}
removeAllListeners(event) {
this.emitter.removeAllListeners(event);
return this;
}
/**
* Disposes of any side-effects this interceptor has introduced.
*/
dispose() {
const logger = this.logger.extend("dispose");
if (this.readyState === InterceptorReadyState.DISPOSED) {
logger.info("cannot dispose, already disposed!");
return;
}
logger.info("disposing the interceptor...");
this.readyState = InterceptorReadyState.DISPOSING;
if (!this.getInstance()) {
logger.info("no interceptors running, skipping dispose...");
return;
}
this.clearInstance();
logger.info("global symbol deleted:", getGlobalSymbol(this.symbol));
if (this.subscriptions.length > 0) {
logger.info("disposing of %d subscriptions...", this.subscriptions.length);
for (const dispose of this.subscriptions) dispose();
this.subscriptions = [];
logger.info("disposed of all subscriptions!", this.subscriptions.length);
}
this.emitter.removeAllListeners();
logger.info("destroyed the listener!");
this.readyState = InterceptorReadyState.DISPOSED;
}
getInstance() {
const instance = getGlobalSymbol(this.symbol);
this.logger.info("retrieved global instance:", instance?.constructor?.name);
return instance;
}
setInstance() {
setGlobalSymbol(this.symbol, this);
this.logger.info("set global instance!", this.symbol.description);
}
clearInstance() {
deleteGlobalSymbol(this.symbol);
this.logger.info("cleared global instance!", this.symbol.description);
}
};
//#endregion
//#region src/createRequestId.ts
/**
* Generate a random ID string to represent a request.
* @example
* createRequestId()
* // "f774b6c9c600f"
*/
function createRequestId() {
return Math.random().toString(16).slice(2);
}
//#endregion
export { deleteGlobalSymbol as a, InterceptorReadyState as i, INTERNAL_REQUEST_ID_HEADER_NAME as n, getGlobalSymbol as o, Interceptor as r, createRequestId as t };
//# sourceMappingURL=createRequestId-DQcIlohW.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,248 @@
import { a as RequestController, i as canParseUrl, n as setRawRequest, r as FetchResponse, s as IS_PATCHED_MODULE } from "./getRawRequest-BTaNLFr0.mjs";
import { r as Interceptor, t as createRequestId } from "./createRequestId-DQcIlohW.mjs";
import { n as emitAsync, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-npXitu1-.mjs";
import { n as isResponseError, t as handleRequest } from "./handleRequest-D7kpTI5U.mjs";
import { DeferredPromise } from "@open-draft/deferred-promise";
import { invariant } from "outvariant";
import { until } from "@open-draft/until";
//#region src/interceptors/fetch/utils/createNetworkError.ts
function createNetworkError(cause) {
return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause });
}
//#endregion
//#region src/interceptors/fetch/utils/followRedirect.ts
const REQUEST_BODY_HEADERS = [
"content-encoding",
"content-language",
"content-location",
"content-type",
"content-length"
];
const kRedirectCount = Symbol("kRedirectCount");
/**
* @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210
*/
async function followFetchRedirect(request, response) {
if (response.status !== 303 && request.body != null) return Promise.reject(createNetworkError());
const requestUrl = new URL(request.url);
let locationUrl;
try {
locationUrl = new URL(response.headers.get("location"), request.url);
} catch (error) {
return Promise.reject(createNetworkError(error));
}
if (!(locationUrl.protocol === "http:" || locationUrl.protocol === "https:")) return Promise.reject(createNetworkError("URL scheme must be a HTTP(S) scheme"));
if (Reflect.get(request, kRedirectCount) > 20) return Promise.reject(createNetworkError("redirect count exceeded"));
Object.defineProperty(request, kRedirectCount, { value: (Reflect.get(request, kRedirectCount) || 0) + 1 });
if (request.mode === "cors" && (locationUrl.username || locationUrl.password) && !sameOrigin(requestUrl, locationUrl)) return Promise.reject(createNetworkError("cross origin not allowed for request mode \"cors\""));
const requestInit = {};
if ([301, 302].includes(response.status) && request.method === "POST" || response.status === 303 && !["HEAD", "GET"].includes(request.method)) {
requestInit.method = "GET";
requestInit.body = null;
REQUEST_BODY_HEADERS.forEach((headerName) => {
request.headers.delete(headerName);
});
}
if (!sameOrigin(requestUrl, locationUrl)) {
request.headers.delete("authorization");
request.headers.delete("proxy-authorization");
request.headers.delete("cookie");
request.headers.delete("host");
}
/**
* @note Undici "safely" extracts the request body.
* I suspect we cannot dispatch this request again
* since its body has been read and the stream is locked.
*/
requestInit.headers = request.headers;
const finalResponse = await fetch(new Request(locationUrl, requestInit));
Object.defineProperty(finalResponse, "redirected", {
value: true,
configurable: true
});
return finalResponse;
}
/**
* @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761
*/
function sameOrigin(left, right) {
if (left.origin === right.origin && left.origin === "null") return true;
if (left.protocol === right.protocol && left.hostname === right.hostname && left.port === right.port) return true;
return false;
}
//#endregion
//#region src/interceptors/fetch/utils/brotli-decompress.browser.ts
var BrotliDecompressionStream = class extends TransformStream {
constructor() {
console.warn("[Interceptors]: Brotli decompression of response streams is not supported in the browser");
super({ transform(chunk, controller) {
controller.enqueue(chunk);
} });
}
};
//#endregion
//#region src/interceptors/fetch/utils/decompression.ts
var PipelineStream = class extends TransformStream {
constructor(transformStreams, ...strategies) {
super({}, ...strategies);
const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform));
Object.defineProperty(this, "readable", { get() {
return readable;
} });
}
};
function parseContentEncoding(contentEncoding) {
return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim());
}
function createDecompressionStream(contentEncoding) {
if (contentEncoding === "") return null;
const codings = parseContentEncoding(contentEncoding);
if (codings.length === 0) return null;
return new PipelineStream(codings.reduceRight((transformers, coding) => {
if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip"));
else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate"));
else if (coding === "br") return transformers.concat(new BrotliDecompressionStream());
else transformers.length = 0;
return transformers;
}, []));
}
function decompressResponse(response) {
if (response.body === null) return null;
const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || "");
if (!decompressionStream) return null;
response.body.pipeTo(decompressionStream.writable);
return decompressionStream.readable;
}
//#endregion
//#region src/interceptors/fetch/index.ts
var FetchInterceptor = class FetchInterceptor extends Interceptor {
static {
this.symbol = Symbol("fetch");
}
constructor() {
super(FetchInterceptor.symbol);
}
checkEnvironment() {
return hasConfigurableGlobal("fetch");
}
async setup() {
const pureFetch = globalThis.fetch;
invariant(!pureFetch[IS_PATCHED_MODULE], "Failed to patch the \"fetch\" module: already patched.");
globalThis.fetch = async (input, init) => {
const requestId = createRequestId();
/**
* @note Resolve potentially relative request URL
* against the present `location`. This is mainly
* for native `fetch` in JSDOM.
* @see https://github.com/mswjs/msw/issues/1625
*/
const resolvedInput = typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.href) : input;
const request = new Request(resolvedInput, init);
/**
* @note Set the raw request only if a Request instance was provided to fetch.
*/
if (input instanceof Request) setRawRequest(request, input);
const responsePromise = new DeferredPromise();
const controller = new RequestController(request, {
passthrough: async () => {
this.logger.info("request has not been handled, passthrough...");
/**
* @note Clone the request instance right before performing it.
* This preserves any modifications made to the intercepted request
* in the "request" listener. This also allows the user to read the
* request body in the "response" listener (otherwise "unusable").
*/
const requestCloneForResponseEvent = request.clone();
const { error: responseError, data: originalResponse } = await until(() => pureFetch(request));
if (responseError) return responsePromise.reject(responseError);
this.logger.info("original fetch performed", originalResponse);
if (this.emitter.listenerCount("response") > 0) {
this.logger.info("emitting the \"response\" event...");
const responseClone = originalResponse.clone();
await emitAsync(this.emitter, "response", {
response: responseClone,
isMockedResponse: false,
request: requestCloneForResponseEvent,
requestId
});
}
responsePromise.resolve(originalResponse);
},
respondWith: async (rawResponse) => {
if (isResponseError(rawResponse)) {
this.logger.info("request has errored!", { response: rawResponse });
responsePromise.reject(createNetworkError(rawResponse));
return;
}
this.logger.info("received mocked response!", { rawResponse });
const decompressedStream = decompressResponse(rawResponse);
const response = decompressedStream === null ? rawResponse : new FetchResponse(decompressedStream, rawResponse);
FetchResponse.setUrl(request.url, response);
/**
* Undici's handling of following redirect responses.
* Treat the "manual" redirect mode as a regular mocked response.
* This way, the client can manually follow the redirect it receives.
* @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173
*/
if (FetchResponse.isRedirectResponse(response.status)) {
if (request.redirect === "error") {
responsePromise.reject(createNetworkError("unexpected redirect"));
return;
}
if (request.redirect === "follow") {
followFetchRedirect(request, response).then((response$1) => {
responsePromise.resolve(response$1);
}, (reason) => {
responsePromise.reject(reason);
});
return;
}
}
if (this.emitter.listenerCount("response") > 0) {
this.logger.info("emitting the \"response\" event...");
await emitAsync(this.emitter, "response", {
response: response.clone(),
isMockedResponse: true,
request,
requestId
});
}
responsePromise.resolve(response);
},
errorWith: (reason) => {
this.logger.info("request has been aborted!", { reason });
responsePromise.reject(reason);
}
});
this.logger.info("[%s] %s", request.method, request.url);
this.logger.info("awaiting for the mocked response...");
this.logger.info("emitting the \"request\" event for %s listener(s)...", this.emitter.listenerCount("request"));
await handleRequest({
request,
requestId,
emitter: this.emitter,
controller
});
return responsePromise;
};
Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
enumerable: true,
configurable: true,
value: true
});
this.subscriptions.push(() => {
Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, { value: void 0 });
globalThis.fetch = pureFetch;
this.logger.info("restored native \"globalThis.fetch\"!", globalThis.fetch.name);
});
}
};
//#endregion
export { FetchInterceptor as t };
//# sourceMappingURL=fetch-DdKEdDOR.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,253 @@
const require_getRawRequest = require('./getRawRequest-zx8rUJL2.cjs');
const require_createRequestId = require('./createRequestId-Cs4oXfa1.cjs');
const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BvCTG97d.cjs');
const require_handleRequest = require('./handleRequest-CvX2G-Lz.cjs');
let _open_draft_deferred_promise = require("@open-draft/deferred-promise");
let outvariant = require("outvariant");
let _open_draft_until = require("@open-draft/until");
//#region src/interceptors/fetch/utils/createNetworkError.ts
function createNetworkError(cause) {
return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause });
}
//#endregion
//#region src/interceptors/fetch/utils/followRedirect.ts
const REQUEST_BODY_HEADERS = [
"content-encoding",
"content-language",
"content-location",
"content-type",
"content-length"
];
const kRedirectCount = Symbol("kRedirectCount");
/**
* @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210
*/
async function followFetchRedirect(request, response) {
if (response.status !== 303 && request.body != null) return Promise.reject(createNetworkError());
const requestUrl = new URL(request.url);
let locationUrl;
try {
locationUrl = new URL(response.headers.get("location"), request.url);
} catch (error) {
return Promise.reject(createNetworkError(error));
}
if (!(locationUrl.protocol === "http:" || locationUrl.protocol === "https:")) return Promise.reject(createNetworkError("URL scheme must be a HTTP(S) scheme"));
if (Reflect.get(request, kRedirectCount) > 20) return Promise.reject(createNetworkError("redirect count exceeded"));
Object.defineProperty(request, kRedirectCount, { value: (Reflect.get(request, kRedirectCount) || 0) + 1 });
if (request.mode === "cors" && (locationUrl.username || locationUrl.password) && !sameOrigin(requestUrl, locationUrl)) return Promise.reject(createNetworkError("cross origin not allowed for request mode \"cors\""));
const requestInit = {};
if ([301, 302].includes(response.status) && request.method === "POST" || response.status === 303 && !["HEAD", "GET"].includes(request.method)) {
requestInit.method = "GET";
requestInit.body = null;
REQUEST_BODY_HEADERS.forEach((headerName) => {
request.headers.delete(headerName);
});
}
if (!sameOrigin(requestUrl, locationUrl)) {
request.headers.delete("authorization");
request.headers.delete("proxy-authorization");
request.headers.delete("cookie");
request.headers.delete("host");
}
/**
* @note Undici "safely" extracts the request body.
* I suspect we cannot dispatch this request again
* since its body has been read and the stream is locked.
*/
requestInit.headers = request.headers;
const finalResponse = await fetch(new Request(locationUrl, requestInit));
Object.defineProperty(finalResponse, "redirected", {
value: true,
configurable: true
});
return finalResponse;
}
/**
* @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761
*/
function sameOrigin(left, right) {
if (left.origin === right.origin && left.origin === "null") return true;
if (left.protocol === right.protocol && left.hostname === right.hostname && left.port === right.port) return true;
return false;
}
//#endregion
//#region src/interceptors/fetch/utils/brotli-decompress.browser.ts
var BrotliDecompressionStream = class extends TransformStream {
constructor() {
console.warn("[Interceptors]: Brotli decompression of response streams is not supported in the browser");
super({ transform(chunk, controller) {
controller.enqueue(chunk);
} });
}
};
//#endregion
//#region src/interceptors/fetch/utils/decompression.ts
var PipelineStream = class extends TransformStream {
constructor(transformStreams, ...strategies) {
super({}, ...strategies);
const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform));
Object.defineProperty(this, "readable", { get() {
return readable;
} });
}
};
function parseContentEncoding(contentEncoding) {
return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim());
}
function createDecompressionStream(contentEncoding) {
if (contentEncoding === "") return null;
const codings = parseContentEncoding(contentEncoding);
if (codings.length === 0) return null;
return new PipelineStream(codings.reduceRight((transformers, coding) => {
if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip"));
else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate"));
else if (coding === "br") return transformers.concat(new BrotliDecompressionStream());
else transformers.length = 0;
return transformers;
}, []));
}
function decompressResponse(response) {
if (response.body === null) return null;
const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || "");
if (!decompressionStream) return null;
response.body.pipeTo(decompressionStream.writable);
return decompressionStream.readable;
}
//#endregion
//#region src/interceptors/fetch/index.ts
var FetchInterceptor = class FetchInterceptor extends require_createRequestId.Interceptor {
static {
this.symbol = Symbol("fetch");
}
constructor() {
super(FetchInterceptor.symbol);
}
checkEnvironment() {
return require_hasConfigurableGlobal.hasConfigurableGlobal("fetch");
}
async setup() {
const pureFetch = globalThis.fetch;
(0, outvariant.invariant)(!pureFetch[require_getRawRequest.IS_PATCHED_MODULE], "Failed to patch the \"fetch\" module: already patched.");
globalThis.fetch = async (input, init) => {
const requestId = require_createRequestId.createRequestId();
/**
* @note Resolve potentially relative request URL
* against the present `location`. This is mainly
* for native `fetch` in JSDOM.
* @see https://github.com/mswjs/msw/issues/1625
*/
const resolvedInput = typeof input === "string" && typeof location !== "undefined" && !require_getRawRequest.canParseUrl(input) ? new URL(input, location.href) : input;
const request = new Request(resolvedInput, init);
/**
* @note Set the raw request only if a Request instance was provided to fetch.
*/
if (input instanceof Request) require_getRawRequest.setRawRequest(request, input);
const responsePromise = new _open_draft_deferred_promise.DeferredPromise();
const controller = new require_getRawRequest.RequestController(request, {
passthrough: async () => {
this.logger.info("request has not been handled, passthrough...");
/**
* @note Clone the request instance right before performing it.
* This preserves any modifications made to the intercepted request
* in the "request" listener. This also allows the user to read the
* request body in the "response" listener (otherwise "unusable").
*/
const requestCloneForResponseEvent = request.clone();
const { error: responseError, data: originalResponse } = await (0, _open_draft_until.until)(() => pureFetch(request));
if (responseError) return responsePromise.reject(responseError);
this.logger.info("original fetch performed", originalResponse);
if (this.emitter.listenerCount("response") > 0) {
this.logger.info("emitting the \"response\" event...");
const responseClone = originalResponse.clone();
await require_hasConfigurableGlobal.emitAsync(this.emitter, "response", {
response: responseClone,
isMockedResponse: false,
request: requestCloneForResponseEvent,
requestId
});
}
responsePromise.resolve(originalResponse);
},
respondWith: async (rawResponse) => {
if (require_handleRequest.isResponseError(rawResponse)) {
this.logger.info("request has errored!", { response: rawResponse });
responsePromise.reject(createNetworkError(rawResponse));
return;
}
this.logger.info("received mocked response!", { rawResponse });
const decompressedStream = decompressResponse(rawResponse);
const response = decompressedStream === null ? rawResponse : new require_getRawRequest.FetchResponse(decompressedStream, rawResponse);
require_getRawRequest.FetchResponse.setUrl(request.url, response);
/**
* Undici's handling of following redirect responses.
* Treat the "manual" redirect mode as a regular mocked response.
* This way, the client can manually follow the redirect it receives.
* @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173
*/
if (require_getRawRequest.FetchResponse.isRedirectResponse(response.status)) {
if (request.redirect === "error") {
responsePromise.reject(createNetworkError("unexpected redirect"));
return;
}
if (request.redirect === "follow") {
followFetchRedirect(request, response).then((response$1) => {
responsePromise.resolve(response$1);
}, (reason) => {
responsePromise.reject(reason);
});
return;
}
}
if (this.emitter.listenerCount("response") > 0) {
this.logger.info("emitting the \"response\" event...");
await require_hasConfigurableGlobal.emitAsync(this.emitter, "response", {
response: response.clone(),
isMockedResponse: true,
request,
requestId
});
}
responsePromise.resolve(response);
},
errorWith: (reason) => {
this.logger.info("request has been aborted!", { reason });
responsePromise.reject(reason);
}
});
this.logger.info("[%s] %s", request.method, request.url);
this.logger.info("awaiting for the mocked response...");
this.logger.info("emitting the \"request\" event for %s listener(s)...", this.emitter.listenerCount("request"));
await require_handleRequest.handleRequest({
request,
requestId,
emitter: this.emitter,
controller
});
return responsePromise;
};
Object.defineProperty(globalThis.fetch, require_getRawRequest.IS_PATCHED_MODULE, {
enumerable: true,
configurable: true,
value: true
});
this.subscriptions.push(() => {
Object.defineProperty(globalThis.fetch, require_getRawRequest.IS_PATCHED_MODULE, { value: void 0 });
globalThis.fetch = pureFetch;
this.logger.info("restored native \"globalThis.fetch\"!", globalThis.fetch.name);
});
}
};
//#endregion
Object.defineProperty(exports, 'FetchInterceptor', {
enumerable: true,
get: function () {
return FetchInterceptor;
}
});
//# sourceMappingURL=fetch-U3v3Y4ap.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,218 @@
import { DeferredPromise } from "@open-draft/deferred-promise";
import { invariant } from "outvariant";
//#region src/glossary.ts
const IS_PATCHED_MODULE = Symbol("isPatchedModule");
//#endregion
//#region src/InterceptorError.ts
var InterceptorError = class InterceptorError extends Error {
constructor(message) {
super(message);
this.name = "InterceptorError";
Object.setPrototypeOf(this, InterceptorError.prototype);
}
};
//#endregion
//#region src/RequestController.ts
var RequestController = class RequestController {
static {
this.PENDING = 0;
}
static {
this.PASSTHROUGH = 1;
}
static {
this.RESPONSE = 2;
}
static {
this.ERROR = 3;
}
constructor(request, source) {
this.request = request;
this.source = source;
this.readyState = RequestController.PENDING;
this.handled = new DeferredPromise();
}
get #handled() {
return this.handled;
}
/**
* Perform this request as-is.
*/
async passthrough() {
invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url);
this.readyState = RequestController.PASSTHROUGH;
await this.source.passthrough();
this.#handled.resolve();
}
/**
* Respond to this request with the given `Response` instance.
*
* @example
* controller.respondWith(new Response())
* controller.respondWith(Response.json({ id }))
* controller.respondWith(Response.error())
*/
respondWith(response) {
invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState);
this.readyState = RequestController.RESPONSE;
this.#handled.resolve();
/**
* @note Although `source.respondWith()` is potentially asynchronous,
* do NOT await it for backward-compatibility. Awaiting it will short-circuit
* the request listener invocation as soon as a listener responds to a request.
* Ideally, that's what we want, but that's not what we promise the user.
*/
this.source.respondWith(response);
}
/**
* Error this request with the given reason.
*
* @example
* controller.errorWith()
* controller.errorWith(new Error('Oops!'))
* controller.errorWith({ message: 'Oops!'})
*/
errorWith(reason) {
invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState);
this.readyState = RequestController.ERROR;
this.source.errorWith(reason);
this.#handled.resolve();
}
};
//#endregion
//#region src/utils/canParseUrl.ts
/**
* Returns a boolean indicating whether the given URL string
* can be parsed into a `URL` instance.
* A substitute for `URL.canParse()` for Node.js 18.
*/
function canParseUrl(url) {
try {
new URL(url);
return true;
} catch (_error) {
return false;
}
}
//#endregion
//#region src/utils/getValueBySymbol.ts
/**
* Returns the value behind the symbol with the given name.
*/
function getValueBySymbol(symbolName, source) {
const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => {
return symbol$1.description === symbolName;
});
if (symbol) return Reflect.get(source, symbol);
}
//#endregion
//#region src/utils/fetchUtils.ts
var FetchResponse = class FetchResponse extends Response {
static {
this.STATUS_CODES_WITHOUT_BODY = [
101,
103,
204,
205,
304
];
}
static {
this.STATUS_CODES_WITH_REDIRECT = [
301,
302,
303,
307,
308
];
}
static isConfigurableStatusCode(status) {
return status >= 200 && status <= 599;
}
static isRedirectResponse(status) {
return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status);
}
/**
* Returns a boolean indicating whether the given response status
* code represents a response that can have a body.
*/
static isResponseWithBody(status) {
return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status);
}
static setUrl(url, response) {
if (!url || url === "about:" || !canParseUrl(url)) return;
const state = getValueBySymbol("state", response);
if (state) state.urlList.push(new URL(url));
else Object.defineProperty(response, "url", {
value: url,
enumerable: true,
configurable: true,
writable: false
});
}
/**
* Parses the given raw HTTP headers into a Fetch API `Headers` instance.
*/
static parseRawHeaders(rawHeaders) {
const headers = new Headers();
for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]);
return headers;
}
constructor(body, init = {}) {
const status = init.status ?? 200;
const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200;
const finalBody = FetchResponse.isResponseWithBody(status) ? body : null;
super(finalBody, {
status: safeStatus,
statusText: init.statusText,
headers: init.headers
});
if (status !== safeStatus) {
/**
* @note Undici keeps an internal "Symbol(state)" that holds
* the actual value of response status. Update that in Node.js.
*/
const state = getValueBySymbol("state", this);
if (state) state.status = status;
else Object.defineProperty(this, "status", {
value: status,
enumerable: true,
configurable: true,
writable: false
});
}
FetchResponse.setUrl(init.url, this);
}
};
//#endregion
//#region src/getRawRequest.ts
const kRawRequest = Symbol("kRawRequest");
/**
* Returns a raw request instance associated with this request.
*
* @example
* interceptor.on('request', ({ request }) => {
* const rawRequest = getRawRequest(request)
*
* if (rawRequest instanceof http.ClientRequest) {
* console.log(rawRequest.rawHeaders)
* }
* })
*/
function getRawRequest(request) {
return Reflect.get(request, kRawRequest);
}
function setRawRequest(request, rawRequest) {
Reflect.set(request, kRawRequest, rawRequest);
}
//#endregion
export { RequestController as a, canParseUrl as i, setRawRequest as n, InterceptorError as o, FetchResponse as r, IS_PATCHED_MODULE as s, getRawRequest as t };
//# sourceMappingURL=getRawRequest-BTaNLFr0.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,259 @@
let _open_draft_deferred_promise = require("@open-draft/deferred-promise");
let outvariant = require("outvariant");
//#region src/glossary.ts
const IS_PATCHED_MODULE = Symbol("isPatchedModule");
//#endregion
//#region src/InterceptorError.ts
var InterceptorError = class InterceptorError extends Error {
constructor(message) {
super(message);
this.name = "InterceptorError";
Object.setPrototypeOf(this, InterceptorError.prototype);
}
};
//#endregion
//#region src/RequestController.ts
var RequestController = class RequestController {
static {
this.PENDING = 0;
}
static {
this.PASSTHROUGH = 1;
}
static {
this.RESPONSE = 2;
}
static {
this.ERROR = 3;
}
constructor(request, source) {
this.request = request;
this.source = source;
this.readyState = RequestController.PENDING;
this.handled = new _open_draft_deferred_promise.DeferredPromise();
}
get #handled() {
return this.handled;
}
/**
* Perform this request as-is.
*/
async passthrough() {
outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url);
this.readyState = RequestController.PASSTHROUGH;
await this.source.passthrough();
this.#handled.resolve();
}
/**
* Respond to this request with the given `Response` instance.
*
* @example
* controller.respondWith(new Response())
* controller.respondWith(Response.json({ id }))
* controller.respondWith(Response.error())
*/
respondWith(response) {
outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState);
this.readyState = RequestController.RESPONSE;
this.#handled.resolve();
/**
* @note Although `source.respondWith()` is potentially asynchronous,
* do NOT await it for backward-compatibility. Awaiting it will short-circuit
* the request listener invocation as soon as a listener responds to a request.
* Ideally, that's what we want, but that's not what we promise the user.
*/
this.source.respondWith(response);
}
/**
* Error this request with the given reason.
*
* @example
* controller.errorWith()
* controller.errorWith(new Error('Oops!'))
* controller.errorWith({ message: 'Oops!'})
*/
errorWith(reason) {
outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState);
this.readyState = RequestController.ERROR;
this.source.errorWith(reason);
this.#handled.resolve();
}
};
//#endregion
//#region src/utils/canParseUrl.ts
/**
* Returns a boolean indicating whether the given URL string
* can be parsed into a `URL` instance.
* A substitute for `URL.canParse()` for Node.js 18.
*/
function canParseUrl(url) {
try {
new URL(url);
return true;
} catch (_error) {
return false;
}
}
//#endregion
//#region src/utils/getValueBySymbol.ts
/**
* Returns the value behind the symbol with the given name.
*/
function getValueBySymbol(symbolName, source) {
const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => {
return symbol$1.description === symbolName;
});
if (symbol) return Reflect.get(source, symbol);
}
//#endregion
//#region src/utils/fetchUtils.ts
var FetchResponse = class FetchResponse extends Response {
static {
this.STATUS_CODES_WITHOUT_BODY = [
101,
103,
204,
205,
304
];
}
static {
this.STATUS_CODES_WITH_REDIRECT = [
301,
302,
303,
307,
308
];
}
static isConfigurableStatusCode(status) {
return status >= 200 && status <= 599;
}
static isRedirectResponse(status) {
return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status);
}
/**
* Returns a boolean indicating whether the given response status
* code represents a response that can have a body.
*/
static isResponseWithBody(status) {
return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status);
}
static setUrl(url, response) {
if (!url || url === "about:" || !canParseUrl(url)) return;
const state = getValueBySymbol("state", response);
if (state) state.urlList.push(new URL(url));
else Object.defineProperty(response, "url", {
value: url,
enumerable: true,
configurable: true,
writable: false
});
}
/**
* Parses the given raw HTTP headers into a Fetch API `Headers` instance.
*/
static parseRawHeaders(rawHeaders) {
const headers = new Headers();
for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]);
return headers;
}
constructor(body, init = {}) {
const status = init.status ?? 200;
const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200;
const finalBody = FetchResponse.isResponseWithBody(status) ? body : null;
super(finalBody, {
status: safeStatus,
statusText: init.statusText,
headers: init.headers
});
if (status !== safeStatus) {
/**
* @note Undici keeps an internal "Symbol(state)" that holds
* the actual value of response status. Update that in Node.js.
*/
const state = getValueBySymbol("state", this);
if (state) state.status = status;
else Object.defineProperty(this, "status", {
value: status,
enumerable: true,
configurable: true,
writable: false
});
}
FetchResponse.setUrl(init.url, this);
}
};
//#endregion
//#region src/getRawRequest.ts
const kRawRequest = Symbol("kRawRequest");
/**
* Returns a raw request instance associated with this request.
*
* @example
* interceptor.on('request', ({ request }) => {
* const rawRequest = getRawRequest(request)
*
* if (rawRequest instanceof http.ClientRequest) {
* console.log(rawRequest.rawHeaders)
* }
* })
*/
function getRawRequest(request) {
return Reflect.get(request, kRawRequest);
}
function setRawRequest(request, rawRequest) {
Reflect.set(request, kRawRequest, rawRequest);
}
//#endregion
Object.defineProperty(exports, 'FetchResponse', {
enumerable: true,
get: function () {
return FetchResponse;
}
});
Object.defineProperty(exports, 'IS_PATCHED_MODULE', {
enumerable: true,
get: function () {
return IS_PATCHED_MODULE;
}
});
Object.defineProperty(exports, 'InterceptorError', {
enumerable: true,
get: function () {
return InterceptorError;
}
});
Object.defineProperty(exports, 'RequestController', {
enumerable: true,
get: function () {
return RequestController;
}
});
Object.defineProperty(exports, 'canParseUrl', {
enumerable: true,
get: function () {
return canParseUrl;
}
});
Object.defineProperty(exports, 'getRawRequest', {
enumerable: true,
get: function () {
return getRawRequest;
}
});
Object.defineProperty(exports, 'setRawRequest', {
enumerable: true,
get: function () {
return setRawRequest;
}
});
//# sourceMappingURL=getRawRequest-zx8rUJL2.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,70 @@
//#region src/RequestController.d.ts
interface RequestControllerSource {
passthrough(): void;
respondWith(response: Response): void;
errorWith(reason?: unknown): void;
}
declare class RequestController {
#private;
protected readonly request: Request;
protected readonly source: RequestControllerSource;
static PENDING: 0;
static PASSTHROUGH: 1;
static RESPONSE: 2;
static ERROR: 3;
readyState: number;
/**
* A Promise that resolves when this controller handles a request.
* See `controller.readyState` for more information on the handling result.
*/
handled: Promise<void>;
constructor(request: Request, source: RequestControllerSource);
/**
* Perform this request as-is.
*/
passthrough(): Promise<void>;
/**
* Respond to this request with the given `Response` instance.
*
* @example
* controller.respondWith(new Response())
* controller.respondWith(Response.json({ id }))
* controller.respondWith(Response.error())
*/
respondWith(response: Response): void;
/**
* Error this request with the given reason.
*
* @example
* controller.errorWith()
* controller.errorWith(new Error('Oops!'))
* controller.errorWith({ message: 'Oops!'})
*/
errorWith(reason?: unknown): void;
}
//#endregion
//#region src/glossary.d.ts
declare const IS_PATCHED_MODULE: unique symbol;
type RequestCredentials = 'omit' | 'include' | 'same-origin';
type HttpRequestEventMap = {
request: [args: {
request: Request;
requestId: string;
controller: RequestController;
}];
response: [args: {
response: Response;
isMockedResponse: boolean;
request: Request;
requestId: string;
}];
unhandledException: [args: {
error: unknown;
request: Request;
requestId: string;
controller: RequestController;
}];
};
//#endregion
export { RequestControllerSource as a, RequestController as i, IS_PATCHED_MODULE as n, RequestCredentials as r, HttpRequestEventMap as t };
//# sourceMappingURL=glossary-BdLS4k1H.d.cts.map

View File

@@ -0,0 +1,70 @@
//#region src/RequestController.d.ts
interface RequestControllerSource {
passthrough(): void;
respondWith(response: Response): void;
errorWith(reason?: unknown): void;
}
declare class RequestController {
#private;
protected readonly request: Request;
protected readonly source: RequestControllerSource;
static PENDING: 0;
static PASSTHROUGH: 1;
static RESPONSE: 2;
static ERROR: 3;
readyState: number;
/**
* A Promise that resolves when this controller handles a request.
* See `controller.readyState` for more information on the handling result.
*/
handled: Promise<void>;
constructor(request: Request, source: RequestControllerSource);
/**
* Perform this request as-is.
*/
passthrough(): Promise<void>;
/**
* Respond to this request with the given `Response` instance.
*
* @example
* controller.respondWith(new Response())
* controller.respondWith(Response.json({ id }))
* controller.respondWith(Response.error())
*/
respondWith(response: Response): void;
/**
* Error this request with the given reason.
*
* @example
* controller.errorWith()
* controller.errorWith(new Error('Oops!'))
* controller.errorWith({ message: 'Oops!'})
*/
errorWith(reason?: unknown): void;
}
//#endregion
//#region src/glossary.d.ts
declare const IS_PATCHED_MODULE: unique symbol;
type RequestCredentials = 'omit' | 'include' | 'same-origin';
type HttpRequestEventMap = {
request: [args: {
request: Request;
requestId: string;
controller: RequestController;
}];
response: [args: {
response: Response;
isMockedResponse: boolean;
request: Request;
requestId: string;
}];
unhandledException: [args: {
error: unknown;
request: Request;
requestId: string;
controller: RequestController;
}];
};
//#endregion
export { RequestControllerSource as a, RequestController as i, IS_PATCHED_MODULE as n, RequestCredentials as r, HttpRequestEventMap as t };
//# sourceMappingURL=glossary-DYwOrogs.d.mts.map

View File

@@ -0,0 +1,189 @@
const require_getRawRequest = require('./getRawRequest-zx8rUJL2.cjs');
const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BvCTG97d.cjs');
let _open_draft_deferred_promise = require("@open-draft/deferred-promise");
let _open_draft_until = require("@open-draft/until");
//#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/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/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 require_getRawRequest.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 _open_draft_deferred_promise.DeferredPromise();
/**
* @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", () => {
requestAbortPromise.reject(options.request.signal.reason);
}, { once: true });
}
const result = await (0, _open_draft_until.until)(async () => {
const requestListenersPromise = require_hasConfigurableGlobal.emitAsync(options.emitter, "request", {
requestId: options.requestId,
request: options.request,
controller: options.controller
});
await Promise.race([
requestAbortPromise,
requestListenersPromise,
options.controller.handled
]);
});
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 require_getRawRequest.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 require_hasConfigurableGlobal.emitAsync(options.emitter, "unhandledException", {
error: result.error,
request: options.request,
requestId: options.requestId,
controller: unhandledExceptionController
});
if (unhandledExceptionController.readyState !== require_getRawRequest.RequestController.PENDING) return;
}
await options.controller.respondWith(createServerErrorResponse(result.error));
return;
}
if (options.controller.readyState === require_getRawRequest.RequestController.PENDING) return await options.controller.passthrough();
return options.controller.handled;
}
//#endregion
Object.defineProperty(exports, 'handleRequest', {
enumerable: true,
get: function () {
return handleRequest;
}
});
Object.defineProperty(exports, 'isResponseError', {
enumerable: true,
get: function () {
return isResponseError;
}
});
//# sourceMappingURL=handleRequest-CvX2G-Lz.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,178 @@
import { a as RequestController, o as InterceptorError } from "./getRawRequest-BTaNLFr0.mjs";
import { n as emitAsync } from "./hasConfigurableGlobal-npXitu1-.mjs";
import { DeferredPromise } from "@open-draft/deferred-promise";
import { until } from "@open-draft/until";
//#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/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/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();
/**
* @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", () => {
requestAbortPromise.reject(options.request.signal.reason);
}, { 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
]);
});
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 { isResponseError as n, handleRequest as t };
//# sourceMappingURL=handleRequest-D7kpTI5U.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
//#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/hasConfigurableGlobal.ts
/**
* Returns a boolean indicating whether the given global property
* is defined and is configurable.
*/
function hasConfigurableGlobal(propertyName) {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, propertyName);
if (typeof descriptor === "undefined") return false;
if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false;
if (typeof descriptor.get === "undefined" && descriptor.value == null) return false;
if (typeof descriptor.set === "undefined" && !descriptor.configurable) {
console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`);
return false;
}
return true;
}
//#endregion
Object.defineProperty(exports, 'emitAsync', {
enumerable: true,
get: function () {
return emitAsync;
}
});
Object.defineProperty(exports, 'hasConfigurableGlobal', {
enumerable: true,
get: function () {
return hasConfigurableGlobal;
}
});
//# sourceMappingURL=hasConfigurableGlobal-BvCTG97d.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hasConfigurableGlobal-BvCTG97d.cjs","names":[],"sources":["../../src/utils/emitAsync.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(propertyName: string): boolean {\n const descriptor = Object.getOwnPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof descriptor === 'undefined') {\n return false\n }\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;;;;;AClBvC,SAAgB,sBAAsB,cAA+B;CACnE,MAAM,aAAa,OAAO,yBAAyB,YAAY,aAAa;AAG5E,KAAI,OAAO,eAAe,YACxB,QAAO;AAIT,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"}

View File

@@ -0,0 +1,33 @@
//#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/hasConfigurableGlobal.ts
/**
* Returns a boolean indicating whether the given global property
* is defined and is configurable.
*/
function hasConfigurableGlobal(propertyName) {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, propertyName);
if (typeof descriptor === "undefined") return false;
if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false;
if (typeof descriptor.get === "undefined" && descriptor.value == null) return false;
if (typeof descriptor.set === "undefined" && !descriptor.configurable) {
console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`);
return false;
}
return true;
}
//#endregion
export { emitAsync as n, hasConfigurableGlobal as t };
//# sourceMappingURL=hasConfigurableGlobal-npXitu1-.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hasConfigurableGlobal-npXitu1-.mjs","names":[],"sources":["../../src/utils/emitAsync.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(propertyName: string): boolean {\n const descriptor = Object.getOwnPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof descriptor === 'undefined') {\n return false\n }\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;;;;;AClBvC,SAAgB,sBAAsB,cAA+B;CACnE,MAAM,aAAa,OAAO,yBAAyB,YAAY,aAAa;AAG5E,KAAI,OAAO,eAAe,YACxB,QAAO;AAIT,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"}

70
node_modules/@mswjs/interceptors/lib/browser/index.cjs generated vendored Normal file
View File

@@ -0,0 +1,70 @@
const require_getRawRequest = require('./getRawRequest-zx8rUJL2.cjs');
const require_createRequestId = require('./createRequestId-Cs4oXfa1.cjs');
const require_bufferUtils = require('./bufferUtils-Uc0eRItL.cjs');
const require_resolveWebSocketUrl = require('./resolveWebSocketUrl-6K6EgqsA.cjs');
//#region src/BatchInterceptor.ts
/**
* A batch interceptor that exposes a single interface
* to apply and operate with multiple interceptors at once.
*/
var BatchInterceptor = class BatchInterceptor extends require_createRequestId.Interceptor {
constructor(options) {
BatchInterceptor.symbol = Symbol(options.name);
super(BatchInterceptor.symbol);
this.interceptors = options.interceptors;
}
setup() {
const logger = this.logger.extend("setup");
logger.info("applying all %d interceptors...", this.interceptors.length);
for (const interceptor of this.interceptors) {
logger.info("applying \"%s\" interceptor...", interceptor.constructor.name);
interceptor.apply();
logger.info("adding interceptor dispose subscription");
this.subscriptions.push(() => interceptor.dispose());
}
}
on(event, listener) {
for (const interceptor of this.interceptors) interceptor.on(event, listener);
return this;
}
once(event, listener) {
for (const interceptor of this.interceptors) interceptor.once(event, listener);
return this;
}
off(event, listener) {
for (const interceptor of this.interceptors) interceptor.off(event, listener);
return this;
}
removeAllListeners(event) {
for (const interceptors of this.interceptors) interceptors.removeAllListeners(event);
return this;
}
};
//#endregion
//#region src/utils/getCleanUrl.ts
/**
* Removes query parameters and hashes from a given URL.
*/
function getCleanUrl(url, isAbsolute = true) {
return [isAbsolute && url.origin, url.pathname].filter(Boolean).join("");
}
//#endregion
exports.BatchInterceptor = BatchInterceptor;
exports.FetchResponse = require_getRawRequest.FetchResponse;
exports.INTERNAL_REQUEST_ID_HEADER_NAME = require_createRequestId.INTERNAL_REQUEST_ID_HEADER_NAME;
exports.IS_PATCHED_MODULE = require_getRawRequest.IS_PATCHED_MODULE;
exports.Interceptor = require_createRequestId.Interceptor;
exports.InterceptorReadyState = require_createRequestId.InterceptorReadyState;
exports.RequestController = require_getRawRequest.RequestController;
exports.createRequestId = require_createRequestId.createRequestId;
exports.decodeBuffer = require_bufferUtils.decodeBuffer;
exports.deleteGlobalSymbol = require_createRequestId.deleteGlobalSymbol;
exports.encodeBuffer = require_bufferUtils.encodeBuffer;
exports.getCleanUrl = getCleanUrl;
exports.getGlobalSymbol = require_createRequestId.getGlobalSymbol;
exports.getRawRequest = require_getRawRequest.getRawRequest;
exports.resolveWebSocketUrl = require_resolveWebSocketUrl.resolveWebSocketUrl;
//# sourceMappingURL=index.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.cjs","names":["Interceptor"],"sources":["../../src/BatchInterceptor.ts","../../src/utils/getCleanUrl.ts"],"sourcesContent":["import { EventMap, Listener } from 'strict-event-emitter'\nimport { Interceptor, ExtractEventNames } from './Interceptor'\n\nexport interface BatchInterceptorOptions<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> {\n name: string\n interceptors: InterceptorList\n}\n\nexport type ExtractEventMapType<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> = InterceptorList extends ReadonlyArray<infer InterceptorType>\n ? InterceptorType extends Interceptor<infer EventMap>\n ? EventMap\n : never\n : never\n\n/**\n * A batch interceptor that exposes a single interface\n * to apply and operate with multiple interceptors at once.\n */\nexport class BatchInterceptor<\n InterceptorList extends ReadonlyArray<Interceptor<any>>,\n Events extends EventMap = ExtractEventMapType<InterceptorList>\n> extends Interceptor<Events> {\n static symbol: symbol\n\n private interceptors: InterceptorList\n\n constructor(options: BatchInterceptorOptions<InterceptorList>) {\n BatchInterceptor.symbol = Symbol(options.name)\n super(BatchInterceptor.symbol)\n this.interceptors = options.interceptors\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('applying all %d interceptors...', this.interceptors.length)\n\n for (const interceptor of this.interceptors) {\n logger.info('applying \"%s\" interceptor...', interceptor.constructor.name)\n interceptor.apply()\n\n logger.info('adding interceptor dispose subscription')\n this.subscriptions.push(() => interceptor.dispose())\n }\n }\n\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n // Instead of adding a listener to the batch interceptor,\n // propagate the listener to each of the individual interceptors.\n for (const interceptor of this.interceptors) {\n interceptor.on(event, listener)\n }\n\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.once(event, listener)\n }\n\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.off(event, listener)\n }\n\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName | undefined\n ): this {\n for (const interceptors of this.interceptors) {\n interceptors.removeAllListeners(event)\n }\n\n return this\n }\n}\n","/**\n * Removes query parameters and hashes from a given URL.\n */\nexport function getCleanUrl(url: URL, isAbsolute: boolean = true): string {\n return [isAbsolute && url.origin, url.pathname].filter(Boolean).join('')\n}\n"],"mappings":";;;;;;;;;;AAsBA,IAAa,mBAAb,MAAa,yBAGHA,oCAAoB;CAK5B,YAAY,SAAmD;AAC7D,mBAAiB,SAAS,OAAO,QAAQ,KAAK;AAC9C,QAAM,iBAAiB,OAAO;AAC9B,OAAK,eAAe,QAAQ;;CAG9B,AAAU,QAAQ;EAChB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAE1C,SAAO,KAAK,mCAAmC,KAAK,aAAa,OAAO;AAExE,OAAK,MAAM,eAAe,KAAK,cAAc;AAC3C,UAAO,KAAK,kCAAgC,YAAY,YAAY,KAAK;AACzE,eAAY,OAAO;AAEnB,UAAO,KAAK,0CAA0C;AACtD,QAAK,cAAc,WAAW,YAAY,SAAS,CAAC;;;CAIxD,AAAO,GACL,OACA,UACM;AAGN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,GAAG,OAAO,SAAS;AAGjC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,KAAK,OAAO,SAAS;AAGnC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,IAAI,OAAO,SAAS;AAGlC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,MAAM,gBAAgB,KAAK,aAC9B,cAAa,mBAAmB,MAAM;AAGxC,SAAO;;;;;;;;;ACzFX,SAAgB,YAAY,KAAU,aAAsB,MAAc;AACxE,QAAO,CAAC,cAAc,IAAI,QAAQ,IAAI,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,GAAG"}

View File

@@ -0,0 +1,96 @@
import { a as RequestControllerSource, i as RequestController, n as IS_PATCHED_MODULE, r as RequestCredentials, t as HttpRequestEventMap } from "./glossary-BdLS4k1H.cjs";
import { a as InterceptorReadyState, c as getGlobalSymbol, i as InterceptorEventMap, n as INTERNAL_REQUEST_ID_HEADER_NAME, o as InterceptorSubscription, r as Interceptor, s as deleteGlobalSymbol, t as ExtractEventNames } from "./Interceptor-Deczogc8.cjs";
import { EventMap, Listener } from "strict-event-emitter";
//#region src/BatchInterceptor.d.ts
interface BatchInterceptorOptions<InterceptorList extends ReadonlyArray<Interceptor<any>>> {
name: string;
interceptors: InterceptorList;
}
type ExtractEventMapType<InterceptorList extends ReadonlyArray<Interceptor<any>>> = InterceptorList extends ReadonlyArray<infer InterceptorType> ? InterceptorType extends Interceptor<infer EventMap> ? EventMap : never : never;
/**
* A batch interceptor that exposes a single interface
* to apply and operate with multiple interceptors at once.
*/
declare class BatchInterceptor<InterceptorList extends ReadonlyArray<Interceptor<any>>, Events extends EventMap = ExtractEventMapType<InterceptorList>> extends Interceptor<Events> {
static symbol: symbol;
private interceptors;
constructor(options: BatchInterceptorOptions<InterceptorList>);
protected setup(): void;
on<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
once<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
off<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
removeAllListeners<EventName extends ExtractEventNames<Events>>(event?: EventName | undefined): this;
}
//#endregion
//#region src/createRequestId.d.ts
/**
* Generate a random ID string to represent a request.
* @example
* createRequestId()
* // "f774b6c9c600f"
*/
declare function createRequestId(): string;
//#endregion
//#region src/utils/getCleanUrl.d.ts
/**
* Removes query parameters and hashes from a given URL.
*/
declare function getCleanUrl(url: URL, isAbsolute?: boolean): string;
//#endregion
//#region src/utils/bufferUtils.d.ts
declare function encodeBuffer(text: string): Uint8Array;
declare function decodeBuffer(buffer: ArrayBuffer, encoding?: string): string;
//#endregion
//#region src/utils/fetchUtils.d.ts
interface FetchResponseInit extends ResponseInit {
url?: string;
}
declare class FetchResponse extends Response {
/**
* Response status codes for responses that cannot have body.
* @see https://fetch.spec.whatwg.org/#statuses
*/
static readonly STATUS_CODES_WITHOUT_BODY: number[];
static readonly STATUS_CODES_WITH_REDIRECT: number[];
static isConfigurableStatusCode(status: number): boolean;
static isRedirectResponse(status: number): boolean;
/**
* Returns a boolean indicating whether the given response status
* code represents a response that can have a body.
*/
static isResponseWithBody(status: number): boolean;
static setUrl(url: string | undefined, response: Response): void;
/**
* Parses the given raw HTTP headers into a Fetch API `Headers` instance.
*/
static parseRawHeaders(rawHeaders: Array<string>): Headers;
constructor(body?: BodyInit | null, init?: FetchResponseInit);
}
//#endregion
//#region src/getRawRequest.d.ts
/**
* Returns a raw request instance associated with this request.
*
* @example
* interceptor.on('request', ({ request }) => {
* const rawRequest = getRawRequest(request)
*
* if (rawRequest instanceof http.ClientRequest) {
* console.log(rawRequest.rawHeaders)
* }
* })
*/
declare function getRawRequest(request: Request): unknown | undefined;
//#endregion
//#region src/utils/resolveWebSocketUrl.d.ts
/**
* Resolve potentially relative WebSocket URLs the same way
* the browser does (replace the protocol, use the origin, etc).
*
* @see https://websockets.spec.whatwg.org//#dom-websocket-websocket
*/
declare function resolveWebSocketUrl(url: string | URL): string;
//#endregion
export { BatchInterceptor, BatchInterceptorOptions, ExtractEventMapType, ExtractEventNames, FetchResponse, HttpRequestEventMap, INTERNAL_REQUEST_ID_HEADER_NAME, IS_PATCHED_MODULE, Interceptor, InterceptorEventMap, InterceptorReadyState, InterceptorSubscription, RequestController, type RequestControllerSource, RequestCredentials, createRequestId, decodeBuffer, deleteGlobalSymbol, encodeBuffer, getCleanUrl, getGlobalSymbol, getRawRequest, resolveWebSocketUrl };
//# sourceMappingURL=index.d.cts.map

View File

@@ -0,0 +1,96 @@
import { a as RequestControllerSource, i as RequestController, n as IS_PATCHED_MODULE, r as RequestCredentials, t as HttpRequestEventMap } from "./glossary-DYwOrogs.mjs";
import { a as InterceptorReadyState, c as getGlobalSymbol, i as InterceptorEventMap, n as INTERNAL_REQUEST_ID_HEADER_NAME, o as InterceptorSubscription, r as Interceptor, s as deleteGlobalSymbol, t as ExtractEventNames } from "./Interceptor-gqKgs-aF.mjs";
import { EventMap, Listener } from "strict-event-emitter";
//#region src/BatchInterceptor.d.ts
interface BatchInterceptorOptions<InterceptorList extends ReadonlyArray<Interceptor<any>>> {
name: string;
interceptors: InterceptorList;
}
type ExtractEventMapType<InterceptorList extends ReadonlyArray<Interceptor<any>>> = InterceptorList extends ReadonlyArray<infer InterceptorType> ? InterceptorType extends Interceptor<infer EventMap> ? EventMap : never : never;
/**
* A batch interceptor that exposes a single interface
* to apply and operate with multiple interceptors at once.
*/
declare class BatchInterceptor<InterceptorList extends ReadonlyArray<Interceptor<any>>, Events extends EventMap = ExtractEventMapType<InterceptorList>> extends Interceptor<Events> {
static symbol: symbol;
private interceptors;
constructor(options: BatchInterceptorOptions<InterceptorList>);
protected setup(): void;
on<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
once<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
off<EventName extends ExtractEventNames<Events>>(event: EventName, listener: Listener<Events[EventName]>): this;
removeAllListeners<EventName extends ExtractEventNames<Events>>(event?: EventName | undefined): this;
}
//#endregion
//#region src/createRequestId.d.ts
/**
* Generate a random ID string to represent a request.
* @example
* createRequestId()
* // "f774b6c9c600f"
*/
declare function createRequestId(): string;
//#endregion
//#region src/utils/getCleanUrl.d.ts
/**
* Removes query parameters and hashes from a given URL.
*/
declare function getCleanUrl(url: URL, isAbsolute?: boolean): string;
//#endregion
//#region src/utils/bufferUtils.d.ts
declare function encodeBuffer(text: string): Uint8Array;
declare function decodeBuffer(buffer: ArrayBuffer, encoding?: string): string;
//#endregion
//#region src/utils/fetchUtils.d.ts
interface FetchResponseInit extends ResponseInit {
url?: string;
}
declare class FetchResponse extends Response {
/**
* Response status codes for responses that cannot have body.
* @see https://fetch.spec.whatwg.org/#statuses
*/
static readonly STATUS_CODES_WITHOUT_BODY: number[];
static readonly STATUS_CODES_WITH_REDIRECT: number[];
static isConfigurableStatusCode(status: number): boolean;
static isRedirectResponse(status: number): boolean;
/**
* Returns a boolean indicating whether the given response status
* code represents a response that can have a body.
*/
static isResponseWithBody(status: number): boolean;
static setUrl(url: string | undefined, response: Response): void;
/**
* Parses the given raw HTTP headers into a Fetch API `Headers` instance.
*/
static parseRawHeaders(rawHeaders: Array<string>): Headers;
constructor(body?: BodyInit | null, init?: FetchResponseInit);
}
//#endregion
//#region src/getRawRequest.d.ts
/**
* Returns a raw request instance associated with this request.
*
* @example
* interceptor.on('request', ({ request }) => {
* const rawRequest = getRawRequest(request)
*
* if (rawRequest instanceof http.ClientRequest) {
* console.log(rawRequest.rawHeaders)
* }
* })
*/
declare function getRawRequest(request: Request): unknown | undefined;
//#endregion
//#region src/utils/resolveWebSocketUrl.d.ts
/**
* Resolve potentially relative WebSocket URLs the same way
* the browser does (replace the protocol, use the origin, etc).
*
* @see https://websockets.spec.whatwg.org//#dom-websocket-websocket
*/
declare function resolveWebSocketUrl(url: string | URL): string;
//#endregion
export { BatchInterceptor, BatchInterceptorOptions, ExtractEventMapType, ExtractEventNames, FetchResponse, HttpRequestEventMap, INTERNAL_REQUEST_ID_HEADER_NAME, IS_PATCHED_MODULE, Interceptor, InterceptorEventMap, InterceptorReadyState, InterceptorSubscription, RequestController, type RequestControllerSource, RequestCredentials, createRequestId, decodeBuffer, deleteGlobalSymbol, encodeBuffer, getCleanUrl, getGlobalSymbol, getRawRequest, resolveWebSocketUrl };
//# sourceMappingURL=index.d.mts.map

56
node_modules/@mswjs/interceptors/lib/browser/index.mjs generated vendored Normal file
View File

@@ -0,0 +1,56 @@
import { a as RequestController, r as FetchResponse, s as IS_PATCHED_MODULE, t as getRawRequest } from "./getRawRequest-BTaNLFr0.mjs";
import { a as deleteGlobalSymbol, i as InterceptorReadyState, n as INTERNAL_REQUEST_ID_HEADER_NAME, o as getGlobalSymbol, r as Interceptor, t as createRequestId } from "./createRequestId-DQcIlohW.mjs";
import { n as encodeBuffer, t as decodeBuffer } from "./bufferUtils-BiiO6HZv.mjs";
import { t as resolveWebSocketUrl } from "./resolveWebSocketUrl-C83-x9iE.mjs";
//#region src/BatchInterceptor.ts
/**
* A batch interceptor that exposes a single interface
* to apply and operate with multiple interceptors at once.
*/
var BatchInterceptor = class BatchInterceptor extends Interceptor {
constructor(options) {
BatchInterceptor.symbol = Symbol(options.name);
super(BatchInterceptor.symbol);
this.interceptors = options.interceptors;
}
setup() {
const logger = this.logger.extend("setup");
logger.info("applying all %d interceptors...", this.interceptors.length);
for (const interceptor of this.interceptors) {
logger.info("applying \"%s\" interceptor...", interceptor.constructor.name);
interceptor.apply();
logger.info("adding interceptor dispose subscription");
this.subscriptions.push(() => interceptor.dispose());
}
}
on(event, listener) {
for (const interceptor of this.interceptors) interceptor.on(event, listener);
return this;
}
once(event, listener) {
for (const interceptor of this.interceptors) interceptor.once(event, listener);
return this;
}
off(event, listener) {
for (const interceptor of this.interceptors) interceptor.off(event, listener);
return this;
}
removeAllListeners(event) {
for (const interceptors of this.interceptors) interceptors.removeAllListeners(event);
return this;
}
};
//#endregion
//#region src/utils/getCleanUrl.ts
/**
* Removes query parameters and hashes from a given URL.
*/
function getCleanUrl(url, isAbsolute = true) {
return [isAbsolute && url.origin, url.pathname].filter(Boolean).join("");
}
//#endregion
export { BatchInterceptor, FetchResponse, INTERNAL_REQUEST_ID_HEADER_NAME, IS_PATCHED_MODULE, Interceptor, InterceptorReadyState, RequestController, createRequestId, decodeBuffer, deleteGlobalSymbol, encodeBuffer, getCleanUrl, getGlobalSymbol, getRawRequest, resolveWebSocketUrl };
//# sourceMappingURL=index.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/BatchInterceptor.ts","../../src/utils/getCleanUrl.ts"],"sourcesContent":["import { EventMap, Listener } from 'strict-event-emitter'\nimport { Interceptor, ExtractEventNames } from './Interceptor'\n\nexport interface BatchInterceptorOptions<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> {\n name: string\n interceptors: InterceptorList\n}\n\nexport type ExtractEventMapType<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> = InterceptorList extends ReadonlyArray<infer InterceptorType>\n ? InterceptorType extends Interceptor<infer EventMap>\n ? EventMap\n : never\n : never\n\n/**\n * A batch interceptor that exposes a single interface\n * to apply and operate with multiple interceptors at once.\n */\nexport class BatchInterceptor<\n InterceptorList extends ReadonlyArray<Interceptor<any>>,\n Events extends EventMap = ExtractEventMapType<InterceptorList>\n> extends Interceptor<Events> {\n static symbol: symbol\n\n private interceptors: InterceptorList\n\n constructor(options: BatchInterceptorOptions<InterceptorList>) {\n BatchInterceptor.symbol = Symbol(options.name)\n super(BatchInterceptor.symbol)\n this.interceptors = options.interceptors\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('applying all %d interceptors...', this.interceptors.length)\n\n for (const interceptor of this.interceptors) {\n logger.info('applying \"%s\" interceptor...', interceptor.constructor.name)\n interceptor.apply()\n\n logger.info('adding interceptor dispose subscription')\n this.subscriptions.push(() => interceptor.dispose())\n }\n }\n\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n // Instead of adding a listener to the batch interceptor,\n // propagate the listener to each of the individual interceptors.\n for (const interceptor of this.interceptors) {\n interceptor.on(event, listener)\n }\n\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.once(event, listener)\n }\n\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.off(event, listener)\n }\n\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName | undefined\n ): this {\n for (const interceptors of this.interceptors) {\n interceptors.removeAllListeners(event)\n }\n\n return this\n }\n}\n","/**\n * Removes query parameters and hashes from a given URL.\n */\nexport function getCleanUrl(url: URL, isAbsolute: boolean = true): string {\n return [isAbsolute && url.origin, url.pathname].filter(Boolean).join('')\n}\n"],"mappings":";;;;;;;;;;AAsBA,IAAa,mBAAb,MAAa,yBAGH,YAAoB;CAK5B,YAAY,SAAmD;AAC7D,mBAAiB,SAAS,OAAO,QAAQ,KAAK;AAC9C,QAAM,iBAAiB,OAAO;AAC9B,OAAK,eAAe,QAAQ;;CAG9B,AAAU,QAAQ;EAChB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAE1C,SAAO,KAAK,mCAAmC,KAAK,aAAa,OAAO;AAExE,OAAK,MAAM,eAAe,KAAK,cAAc;AAC3C,UAAO,KAAK,kCAAgC,YAAY,YAAY,KAAK;AACzE,eAAY,OAAO;AAEnB,UAAO,KAAK,0CAA0C;AACtD,QAAK,cAAc,WAAW,YAAY,SAAS,CAAC;;;CAIxD,AAAO,GACL,OACA,UACM;AAGN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,GAAG,OAAO,SAAS;AAGjC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,KAAK,OAAO,SAAS;AAGnC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,IAAI,OAAO,SAAS;AAGlC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,MAAM,gBAAgB,KAAK,aAC9B,cAAa,mBAAmB,MAAM;AAGxC,SAAO;;;;;;;;;ACzFX,SAAgB,YAAY,KAAU,aAAsB,MAAc;AACxE,QAAO,CAAC,cAAc,IAAI,QAAQ,IAAI,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,GAAG"}

View File

@@ -0,0 +1,622 @@
const require_createRequestId = require('../../createRequestId-Cs4oXfa1.cjs');
const require_resolveWebSocketUrl = require('../../resolveWebSocketUrl-6K6EgqsA.cjs');
const require_hasConfigurableGlobal = require('../../hasConfigurableGlobal-BvCTG97d.cjs');
let _open_draft_deferred_promise = require("@open-draft/deferred-promise");
let outvariant = require("outvariant");
//#region src/interceptors/WebSocket/utils/bindEvent.ts
function bindEvent(target, event) {
Object.defineProperties(event, {
target: {
value: target,
enumerable: true,
writable: true
},
currentTarget: {
value: target,
enumerable: true,
writable: true
}
});
return event;
}
//#endregion
//#region src/interceptors/WebSocket/utils/events.ts
const kCancelable = Symbol("kCancelable");
const kDefaultPrevented = Symbol("kDefaultPrevented");
/**
* A `MessageEvent` superset that supports event cancellation
* in Node.js. It's rather non-intrusive so it can be safely
* used in the browser as well.
*
* @see https://github.com/nodejs/node/issues/51767
*/
var CancelableMessageEvent = class extends MessageEvent {
constructor(type, init) {
super(type, init);
this[kCancelable] = !!init.cancelable;
this[kDefaultPrevented] = false;
}
get cancelable() {
return this[kCancelable];
}
set cancelable(nextCancelable) {
this[kCancelable] = nextCancelable;
}
get defaultPrevented() {
return this[kDefaultPrevented];
}
set defaultPrevented(nextDefaultPrevented) {
this[kDefaultPrevented] = nextDefaultPrevented;
}
preventDefault() {
if (this.cancelable && !this[kDefaultPrevented]) this[kDefaultPrevented] = true;
}
};
var CloseEvent = class extends Event {
constructor(type, init = {}) {
super(type, init);
this.code = init.code === void 0 ? 0 : init.code;
this.reason = init.reason === void 0 ? "" : init.reason;
this.wasClean = init.wasClean === void 0 ? false : init.wasClean;
}
};
var CancelableCloseEvent = class extends CloseEvent {
constructor(type, init = {}) {
super(type, init);
this[kCancelable] = !!init.cancelable;
this[kDefaultPrevented] = false;
}
get cancelable() {
return this[kCancelable];
}
set cancelable(nextCancelable) {
this[kCancelable] = nextCancelable;
}
get defaultPrevented() {
return this[kDefaultPrevented];
}
set defaultPrevented(nextDefaultPrevented) {
this[kDefaultPrevented] = nextDefaultPrevented;
}
preventDefault() {
if (this.cancelable && !this[kDefaultPrevented]) this[kDefaultPrevented] = true;
}
};
//#endregion
//#region src/interceptors/WebSocket/WebSocketClientConnection.ts
const kEmitter$1 = Symbol("kEmitter");
const kBoundListener$1 = Symbol("kBoundListener");
var WebSocketClientConnectionProtocol = class {};
/**
* The WebSocket client instance represents an incoming
* client connection. The user can control the connection,
* send and receive events.
*/
var WebSocketClientConnection = class {
constructor(socket, transport) {
this.socket = socket;
this.transport = transport;
this.id = require_createRequestId.createRequestId();
this.url = new URL(socket.url);
this[kEmitter$1] = new EventTarget();
this.transport.addEventListener("outgoing", (event) => {
const message = bindEvent(this.socket, new CancelableMessageEvent("message", {
data: event.data,
origin: event.origin,
cancelable: true
}));
this[kEmitter$1].dispatchEvent(message);
if (message.defaultPrevented) event.preventDefault();
});
/**
* Emit the "close" event on the "client" connection
* whenever the underlying transport is closed.
* @note "client.close()" does NOT dispatch the "close"
* event on the WebSocket because it uses non-configurable
* close status code. Thus, we listen to the transport
* instead of the WebSocket's "close" event.
*/
this.transport.addEventListener("close", (event) => {
this[kEmitter$1].dispatchEvent(bindEvent(this.socket, new CloseEvent("close", event)));
});
}
/**
* Listen for the outgoing events from the connected WebSocket client.
*/
addEventListener(type, listener, options) {
if (!Reflect.has(listener, kBoundListener$1)) {
const boundListener = listener.bind(this.socket);
Object.defineProperty(listener, kBoundListener$1, {
value: boundListener,
enumerable: false,
configurable: false
});
}
this[kEmitter$1].addEventListener(type, Reflect.get(listener, kBoundListener$1), options);
}
/**
* Removes the listener for the given event.
*/
removeEventListener(event, listener, options) {
this[kEmitter$1].removeEventListener(event, Reflect.get(listener, kBoundListener$1), options);
}
/**
* Send data to the connected client.
*/
send(data) {
this.transport.send(data);
}
/**
* Close the WebSocket connection.
* @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).
* @param {string} reason A custom connection close reason.
*/
close(code, reason) {
this.transport.close(code, reason);
}
};
//#endregion
//#region src/interceptors/WebSocket/WebSocketOverride.ts
const WEBSOCKET_CLOSE_CODE_RANGE_ERROR = "InvalidAccessError: close code out of user configurable range";
const kPassthroughPromise = Symbol("kPassthroughPromise");
const kOnSend = Symbol("kOnSend");
const kClose = Symbol("kClose");
var WebSocketOverride = class extends EventTarget {
static {
this.CONNECTING = 0;
}
static {
this.OPEN = 1;
}
static {
this.CLOSING = 2;
}
static {
this.CLOSED = 3;
}
constructor(url, protocols) {
super();
this.CONNECTING = 0;
this.OPEN = 1;
this.CLOSING = 2;
this.CLOSED = 3;
this._onopen = null;
this._onmessage = null;
this._onerror = null;
this._onclose = null;
this.url = require_resolveWebSocketUrl.resolveWebSocketUrl(url);
this.protocol = "";
this.extensions = "";
this.binaryType = "blob";
this.readyState = this.CONNECTING;
this.bufferedAmount = 0;
this[kPassthroughPromise] = new _open_draft_deferred_promise.DeferredPromise();
queueMicrotask(async () => {
if (await this[kPassthroughPromise]) return;
this.protocol = typeof protocols === "string" ? protocols : Array.isArray(protocols) && protocols.length > 0 ? protocols[0] : "";
/**
* @note Check that nothing has prevented this connection
* (e.g. called `client.close()` in the connection listener).
* If the connection has been prevented, never dispatch the open event,.
*/
if (this.readyState === this.CONNECTING) {
this.readyState = this.OPEN;
this.dispatchEvent(bindEvent(this, new Event("open")));
}
});
}
set onopen(listener) {
this.removeEventListener("open", this._onopen);
this._onopen = listener;
if (listener !== null) this.addEventListener("open", listener);
}
get onopen() {
return this._onopen;
}
set onmessage(listener) {
this.removeEventListener("message", this._onmessage);
this._onmessage = listener;
if (listener !== null) this.addEventListener("message", listener);
}
get onmessage() {
return this._onmessage;
}
set onerror(listener) {
this.removeEventListener("error", this._onerror);
this._onerror = listener;
if (listener !== null) this.addEventListener("error", listener);
}
get onerror() {
return this._onerror;
}
set onclose(listener) {
this.removeEventListener("close", this._onclose);
this._onclose = listener;
if (listener !== null) this.addEventListener("close", listener);
}
get onclose() {
return this._onclose;
}
/**
* @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0
*/
send(data) {
if (this.readyState === this.CONNECTING) {
this.close();
throw new DOMException("InvalidStateError");
}
if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) return;
this.bufferedAmount += getDataSize(data);
queueMicrotask(() => {
this.bufferedAmount = 0;
/**
* @note Notify the parent about outgoing data.
* This notifies the transport and the connection
* listens to the outgoing data to emit the "message" event.
*/
this[kOnSend]?.(data);
});
}
close(code = 1e3, reason) {
(0, outvariant.invariant)(code, WEBSOCKET_CLOSE_CODE_RANGE_ERROR);
(0, outvariant.invariant)(code === 1e3 || code >= 3e3 && code <= 4999, WEBSOCKET_CLOSE_CODE_RANGE_ERROR);
this[kClose](code, reason);
}
[kClose](code = 1e3, reason, wasClean = true) {
/**
* @note Move this check here so that even internal closures,
* like those triggered by the `server` connection, are not
* performed twice.
*/
if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) return;
this.readyState = this.CLOSING;
queueMicrotask(() => {
this.readyState = this.CLOSED;
this.dispatchEvent(bindEvent(this, new CloseEvent("close", {
code,
reason,
wasClean
})));
this._onopen = null;
this._onmessage = null;
this._onerror = null;
this._onclose = null;
});
}
addEventListener(type, listener, options) {
return super.addEventListener(type, listener, options);
}
removeEventListener(type, callback, options) {
return super.removeEventListener(type, callback, options);
}
};
function getDataSize(data) {
if (typeof data === "string") return data.length;
if (data instanceof Blob) return data.size;
return data.byteLength;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketServerConnection.ts
const kEmitter = Symbol("kEmitter");
const kBoundListener = Symbol("kBoundListener");
const kSend = Symbol("kSend");
var WebSocketServerConnectionProtocol = class {};
/**
* The WebSocket server instance represents the actual production
* WebSocket server connection. It's idle by default but you can
* establish it by calling `server.connect()`.
*/
var WebSocketServerConnection = class {
constructor(client, transport, createConnection) {
this.client = client;
this.transport = transport;
this.createConnection = createConnection;
this[kEmitter] = new EventTarget();
this.mockCloseController = new AbortController();
this.realCloseController = new AbortController();
this.transport.addEventListener("outgoing", (event) => {
if (typeof this.realWebSocket === "undefined") return;
queueMicrotask(() => {
if (!event.defaultPrevented)
/**
* @note Use the internal send mechanism so consumers can tell
* apart direct user calls to `server.send()` and internal calls.
* E.g. MSW has to ignore this internal call to log out messages correctly.
*/
this[kSend](event.data);
});
});
this.transport.addEventListener("incoming", this.handleIncomingMessage.bind(this));
}
/**
* The `WebSocket` instance connected to the original server.
* Accessing this before calling `server.connect()` will throw.
*/
get socket() {
(0, outvariant.invariant)(this.realWebSocket, "Cannot access \"socket\" on the original WebSocket server object: the connection is not open. Did you forget to call `server.connect()`?");
return this.realWebSocket;
}
/**
* Open connection to the original WebSocket server.
*/
connect() {
(0, outvariant.invariant)(!this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN, "Failed to call \"connect()\" on the original WebSocket instance: the connection already open");
const realWebSocket = this.createConnection();
realWebSocket.binaryType = this.client.binaryType;
realWebSocket.addEventListener("open", (event) => {
this[kEmitter].dispatchEvent(bindEvent(this.realWebSocket, new Event("open", event)));
}, { once: true });
realWebSocket.addEventListener("message", (event) => {
this.transport.dispatchEvent(bindEvent(this.realWebSocket, new MessageEvent("incoming", {
data: event.data,
origin: event.origin
})));
});
this.client.addEventListener("close", (event) => {
this.handleMockClose(event);
}, { signal: this.mockCloseController.signal });
realWebSocket.addEventListener("close", (event) => {
this.handleRealClose(event);
}, { signal: this.realCloseController.signal });
realWebSocket.addEventListener("error", () => {
const errorEvent = bindEvent(realWebSocket, new Event("error", { cancelable: true }));
this[kEmitter].dispatchEvent(errorEvent);
if (!errorEvent.defaultPrevented) this.client.dispatchEvent(bindEvent(this.client, new Event("error")));
});
this.realWebSocket = realWebSocket;
}
/**
* Listen for the incoming events from the original WebSocket server.
*/
addEventListener(event, listener, options) {
if (!Reflect.has(listener, kBoundListener)) {
const boundListener = listener.bind(this.client);
Object.defineProperty(listener, kBoundListener, {
value: boundListener,
enumerable: false
});
}
this[kEmitter].addEventListener(event, Reflect.get(listener, kBoundListener), options);
}
/**
* Remove the listener for the given event.
*/
removeEventListener(event, listener, options) {
this[kEmitter].removeEventListener(event, Reflect.get(listener, kBoundListener), options);
}
/**
* Send data to the original WebSocket server.
* @example
* server.send('hello')
* server.send(new Blob(['hello']))
* server.send(new TextEncoder().encode('hello'))
*/
send(data) {
this[kSend](data);
}
[kSend](data) {
const { realWebSocket } = this;
(0, outvariant.invariant)(realWebSocket, "Failed to call \"server.send()\" for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?", this.client.url);
if (realWebSocket.readyState === WebSocket.CLOSING || realWebSocket.readyState === WebSocket.CLOSED) return;
if (realWebSocket.readyState === WebSocket.CONNECTING) {
realWebSocket.addEventListener("open", () => {
realWebSocket.send(data);
}, { once: true });
return;
}
realWebSocket.send(data);
}
/**
* Close the actual server connection.
*/
close() {
const { realWebSocket } = this;
(0, outvariant.invariant)(realWebSocket, "Failed to close server connection for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?", this.client.url);
this.realCloseController.abort();
if (realWebSocket.readyState === WebSocket.CLOSING || realWebSocket.readyState === WebSocket.CLOSED) return;
realWebSocket.close();
queueMicrotask(() => {
this[kEmitter].dispatchEvent(bindEvent(this.realWebSocket, new CancelableCloseEvent("close", {
code: 1e3,
cancelable: true
})));
});
}
handleIncomingMessage(event) {
const messageEvent = bindEvent(event.target, new CancelableMessageEvent("message", {
data: event.data,
origin: event.origin,
cancelable: true
}));
/**
* @note Emit "message" event on the server connection
* instance to let the interceptor know about these
* incoming events from the original server. In that listener,
* the interceptor can modify or skip the event forwarding
* to the mock WebSocket instance.
*/
this[kEmitter].dispatchEvent(messageEvent);
/**
* @note Forward the incoming server events to the client.
* Preventing the default on the message event stops this.
*/
if (!messageEvent.defaultPrevented) this.client.dispatchEvent(bindEvent(
/**
* @note Bind the forwarded original server events
* to the mock WebSocket instance so it would
* dispatch them straight away.
*/
this.client,
new MessageEvent("message", {
data: event.data,
origin: event.origin
})
));
}
handleMockClose(_event) {
if (this.realWebSocket) this.realWebSocket.close();
}
handleRealClose(event) {
this.mockCloseController.abort();
const closeEvent = bindEvent(this.realWebSocket, new CancelableCloseEvent("close", {
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
cancelable: true
}));
this[kEmitter].dispatchEvent(closeEvent);
if (!closeEvent.defaultPrevented) this.client[kClose](event.code, event.reason);
}
};
//#endregion
//#region src/interceptors/WebSocket/WebSocketClassTransport.ts
/**
* Abstraction over the given mock `WebSocket` instance that allows
* for controlling that instance (e.g. sending and receiving messages).
*/
var WebSocketClassTransport = class extends EventTarget {
constructor(socket) {
super();
this.socket = socket;
this.socket.addEventListener("close", (event) => {
this.dispatchEvent(bindEvent(this.socket, new CloseEvent("close", event)));
});
/**
* Emit the "outgoing" event on the transport
* whenever the WebSocket client sends data ("ws.send()").
*/
this.socket[kOnSend] = (data) => {
this.dispatchEvent(bindEvent(this.socket, new CancelableMessageEvent("outgoing", {
data,
origin: this.socket.url,
cancelable: true
})));
};
}
addEventListener(type, callback, options) {
return super.addEventListener(type, callback, options);
}
dispatchEvent(event) {
return super.dispatchEvent(event);
}
send(data) {
queueMicrotask(() => {
if (this.socket.readyState === this.socket.CLOSING || this.socket.readyState === this.socket.CLOSED) return;
const dispatchEvent = () => {
this.socket.dispatchEvent(bindEvent(
/**
* @note Setting this event's "target" to the
* WebSocket override instance is important.
* This way it can tell apart original incoming events
* (must be forwarded to the transport) from the
* mocked message events like the one below
* (must be dispatched on the client instance).
*/
this.socket,
new MessageEvent("message", {
data,
origin: this.socket.url
})
));
};
if (this.socket.readyState === this.socket.CONNECTING) this.socket.addEventListener("open", () => {
dispatchEvent();
}, { once: true });
else dispatchEvent();
});
}
close(code, reason) {
/**
* @note Call the internal close method directly
* to allow closing the connection with the status codes
* that are non-configurable by the user (> 1000 <= 1015).
*/
this.socket[kClose](code, reason);
}
};
//#endregion
//#region src/interceptors/WebSocket/index.ts
/**
* Intercept the outgoing WebSocket connections created using
* the global `WebSocket` class.
*/
var WebSocketInterceptor = class WebSocketInterceptor extends require_createRequestId.Interceptor {
static {
this.symbol = Symbol("websocket");
}
constructor() {
super(WebSocketInterceptor.symbol);
}
checkEnvironment() {
return require_hasConfigurableGlobal.hasConfigurableGlobal("WebSocket");
}
setup() {
const originalWebSocketDescriptor = Object.getOwnPropertyDescriptor(globalThis, "WebSocket");
const WebSocketProxy = new Proxy(globalThis.WebSocket, { construct: (target, args, newTarget) => {
const [url, protocols] = args;
const createConnection = () => {
return Reflect.construct(target, args, newTarget);
};
const socket = new WebSocketOverride(url, protocols);
const transport = new WebSocketClassTransport(socket);
queueMicrotask(async () => {
try {
const server = new WebSocketServerConnection(socket, transport, createConnection);
const hasConnectionListeners = this.emitter.listenerCount("connection") > 0;
await require_hasConfigurableGlobal.emitAsync(this.emitter, "connection", {
client: new WebSocketClientConnection(socket, transport),
server,
info: { protocols }
});
if (hasConnectionListeners) socket[kPassthroughPromise].resolve(false);
else {
socket[kPassthroughPromise].resolve(true);
server.connect();
server.addEventListener("open", () => {
socket.dispatchEvent(bindEvent(socket, new Event("open")));
if (server["realWebSocket"]) socket.protocol = server["realWebSocket"].protocol;
});
}
} catch (error) {
/**
* @note Translate unhandled exceptions during the connection
* handling (i.e. interceptor exceptions) as WebSocket connection
* closures with error. This prevents from the exceptions occurring
* in `queueMicrotask` from being process-wide and uncatchable.
*/
if (error instanceof Error) {
socket.dispatchEvent(new Event("error"));
if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) socket[kClose](1011, error.message, false);
console.error(error);
}
}
});
return socket;
} });
Object.defineProperty(globalThis, "WebSocket", {
value: WebSocketProxy,
configurable: true
});
this.subscriptions.push(() => {
Object.defineProperty(globalThis, "WebSocket", originalWebSocketDescriptor);
});
}
};
//#endregion
exports.CancelableCloseEvent = CancelableCloseEvent;
exports.CancelableMessageEvent = CancelableMessageEvent;
exports.CloseEvent = CloseEvent;
exports.WebSocketClientConnection = WebSocketClientConnection;
exports.WebSocketClientConnectionProtocol = WebSocketClientConnectionProtocol;
exports.WebSocketInterceptor = WebSocketInterceptor;
exports.WebSocketServerConnection = WebSocketServerConnection;
exports.WebSocketServerConnectionProtocol = WebSocketServerConnectionProtocol;
//# sourceMappingURL=index.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,277 @@
import { r as Interceptor } from "../../Interceptor-Deczogc8.cjs";
//#region src/interceptors/WebSocket/utils/events.d.ts
declare const kCancelable: unique symbol;
declare const kDefaultPrevented: unique symbol;
/**
* A `MessageEvent` superset that supports event cancellation
* in Node.js. It's rather non-intrusive so it can be safely
* used in the browser as well.
*
* @see https://github.com/nodejs/node/issues/51767
*/
declare class CancelableMessageEvent<T = any> extends MessageEvent<T> {
[kCancelable]: boolean;
[kDefaultPrevented]: boolean;
constructor(type: string, init: MessageEventInit<T>);
get cancelable(): boolean;
set cancelable(nextCancelable: boolean);
get defaultPrevented(): boolean;
set defaultPrevented(nextDefaultPrevented: boolean);
preventDefault(): void;
}
interface CloseEventInit extends EventInit {
code?: number;
reason?: string;
wasClean?: boolean;
}
declare class CloseEvent extends Event {
code: number;
reason: string;
wasClean: boolean;
constructor(type: string, init?: CloseEventInit);
}
declare class CancelableCloseEvent extends CloseEvent {
[kCancelable]: boolean;
[kDefaultPrevented]: boolean;
constructor(type: string, init?: CloseEventInit);
get cancelable(): boolean;
set cancelable(nextCancelable: boolean);
get defaultPrevented(): boolean;
set defaultPrevented(nextDefaultPrevented: boolean);
preventDefault(): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketTransport.d.ts
type WebSocketData = string | ArrayBufferLike | Blob | ArrayBufferView;
type WebSocketTransportEventMap = {
incoming: MessageEvent<WebSocketData>;
outgoing: MessageEvent<WebSocketData>;
close: CloseEvent;
};
type StrictEventListenerOrEventListenerObject<EventType extends Event> = ((this: WebSocket, event: EventType) => void) | {
handleEvent(this: WebSocket, event: EventType): void;
};
interface WebSocketTransport {
addEventListener<EventType extends keyof WebSocketTransportEventMap>(event: EventType, listener: StrictEventListenerOrEventListenerObject<WebSocketTransportEventMap[EventType]> | null, options?: boolean | AddEventListenerOptions): void;
dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(event: WebSocketTransportEventMap[EventType]): boolean;
/**
* Send the data from the server to this client.
*/
send(data: WebSocketData): void;
/**
* Close the client connection.
*/
close(code?: number, reason?: string): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketOverride.d.ts
type WebSocketEventListener<EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event> = (this: WebSocket, event: EventType) => void;
declare const kPassthroughPromise: unique symbol;
declare const kOnSend: unique symbol;
declare const kClose: unique symbol;
declare class WebSocketOverride extends EventTarget implements WebSocket {
static readonly CONNECTING = 0;
static readonly OPEN = 1;
static readonly CLOSING = 2;
static readonly CLOSED = 3;
readonly CONNECTING = 0;
readonly OPEN = 1;
readonly CLOSING = 2;
readonly CLOSED = 3;
url: string;
protocol: string;
extensions: string;
binaryType: BinaryType;
readyState: WebSocket['readyState'];
bufferedAmount: number;
private _onopen;
private _onmessage;
private _onerror;
private _onclose;
private [kPassthroughPromise];
private [kOnSend]?;
constructor(url: string | URL, protocols?: string | Array<string>);
set onopen(listener: WebSocketEventListener | null);
get onopen(): WebSocketEventListener | null;
set onmessage(listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null);
get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null;
set onerror(listener: WebSocketEventListener | null);
get onerror(): WebSocketEventListener | null;
set onclose(listener: WebSocketEventListener<CloseEvent> | null);
get onclose(): WebSocketEventListener<CloseEvent> | null;
/**
* @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0
*/
send(data: WebSocketData): void;
close(code?: number, reason?: string): void;
private [kClose];
addEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, event: WebSocketEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof WebSocketEventMap>(type: K, callback: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketClientConnection.d.ts
declare const kEmitter$1: unique symbol;
interface WebSocketClientEventMap {
message: MessageEvent<WebSocketData>;
close: CloseEvent;
}
declare abstract class WebSocketClientConnectionProtocol {
abstract id: string;
abstract url: URL;
abstract send(data: WebSocketData): void;
abstract close(code?: number, reason?: string): void;
abstract addEventListener<EventType extends keyof WebSocketClientEventMap>(type: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
abstract removeEventListener<EventType extends keyof WebSocketClientEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
}
/**
* The WebSocket client instance represents an incoming
* client connection. The user can control the connection,
* send and receive events.
*/
declare class WebSocketClientConnection implements WebSocketClientConnectionProtocol {
readonly socket: WebSocket;
private readonly transport;
readonly id: string;
readonly url: URL;
private [kEmitter$1];
constructor(socket: WebSocket, transport: WebSocketTransport);
/**
* Listen for the outgoing events from the connected WebSocket client.
*/
addEventListener<EventType extends keyof WebSocketClientEventMap>(type: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
/**
* Removes the listener for the given event.
*/
removeEventListener<EventType extends keyof WebSocketClientEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
/**
* Send data to the connected client.
*/
send(data: WebSocketData): void;
/**
* Close the WebSocket connection.
* @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).
* @param {string} reason A custom connection close reason.
*/
close(code?: number, reason?: string): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketClassTransport.d.ts
/**
* Abstraction over the given mock `WebSocket` instance that allows
* for controlling that instance (e.g. sending and receiving messages).
*/
declare class WebSocketClassTransport extends EventTarget implements WebSocketTransport {
protected readonly socket: WebSocketOverride;
constructor(socket: WebSocketOverride);
addEventListener<EventType extends keyof WebSocketTransportEventMap>(type: EventType, callback: StrictEventListenerOrEventListenerObject<WebSocketTransportEventMap[EventType]> | null, options?: boolean | AddEventListenerOptions): void;
dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(event: WebSocketTransportEventMap[EventType]): boolean;
send(data: WebSocketData): void;
close(code: number, reason?: string): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketServerConnection.d.ts
declare const kEmitter: unique symbol;
declare const kSend: unique symbol;
interface WebSocketServerEventMap {
open: Event;
message: MessageEvent<WebSocketData>;
error: Event;
close: CloseEvent;
}
declare abstract class WebSocketServerConnectionProtocol {
abstract connect(): void;
abstract send(data: WebSocketData): void;
abstract close(): void;
abstract addEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
abstract removeEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
}
/**
* The WebSocket server instance represents the actual production
* WebSocket server connection. It's idle by default but you can
* establish it by calling `server.connect()`.
*/
declare class WebSocketServerConnection implements WebSocketServerConnectionProtocol {
private readonly client;
private readonly transport;
private readonly createConnection;
/**
* A WebSocket instance connected to the original server.
*/
private realWebSocket?;
private mockCloseController;
private realCloseController;
private [kEmitter];
constructor(client: WebSocketOverride, transport: WebSocketClassTransport, createConnection: () => WebSocket);
/**
* The `WebSocket` instance connected to the original server.
* Accessing this before calling `server.connect()` will throw.
*/
get socket(): WebSocket;
/**
* Open connection to the original WebSocket server.
*/
connect(): void;
/**
* Listen for the incoming events from the original WebSocket server.
*/
addEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
/**
* Remove the listener for the given event.
*/
removeEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
/**
* Send data to the original WebSocket server.
* @example
* server.send('hello')
* server.send(new Blob(['hello']))
* server.send(new TextEncoder().encode('hello'))
*/
send(data: WebSocketData): void;
private [kSend];
/**
* Close the actual server connection.
*/
close(): void;
private handleIncomingMessage;
private handleMockClose;
private handleRealClose;
}
//#endregion
//#region src/interceptors/WebSocket/index.d.ts
type WebSocketEventMap$1 = {
connection: [args: WebSocketConnectionData];
};
type WebSocketConnectionData = {
/**
* The incoming WebSocket client connection.
*/
client: WebSocketClientConnection;
/**
* The original WebSocket server connection.
*/
server: WebSocketServerConnection;
/**
* The connection information.
*/
info: {
/**
* The protocols supported by the WebSocket client.
*/
protocols: string | Array<string> | undefined;
};
};
/**
* Intercept the outgoing WebSocket connections created using
* the global `WebSocket` class.
*/
declare class WebSocketInterceptor extends Interceptor<WebSocketEventMap$1> {
static symbol: symbol;
constructor();
protected checkEnvironment(): boolean;
protected setup(): void;
}
//#endregion
export { CancelableCloseEvent, CancelableMessageEvent, CloseEvent, WebSocketClientConnection, WebSocketClientConnectionProtocol, WebSocketClientEventMap, WebSocketConnectionData, type WebSocketData, WebSocketEventMap$1 as WebSocketEventMap, WebSocketInterceptor, WebSocketServerConnection, WebSocketServerConnectionProtocol, WebSocketServerEventMap, type WebSocketTransport };
//# sourceMappingURL=index.d.cts.map

View File

@@ -0,0 +1,277 @@
import { r as Interceptor } from "../../Interceptor-gqKgs-aF.mjs";
//#region src/interceptors/WebSocket/utils/events.d.ts
declare const kCancelable: unique symbol;
declare const kDefaultPrevented: unique symbol;
/**
* A `MessageEvent` superset that supports event cancellation
* in Node.js. It's rather non-intrusive so it can be safely
* used in the browser as well.
*
* @see https://github.com/nodejs/node/issues/51767
*/
declare class CancelableMessageEvent<T = any> extends MessageEvent<T> {
[kCancelable]: boolean;
[kDefaultPrevented]: boolean;
constructor(type: string, init: MessageEventInit<T>);
get cancelable(): boolean;
set cancelable(nextCancelable: boolean);
get defaultPrevented(): boolean;
set defaultPrevented(nextDefaultPrevented: boolean);
preventDefault(): void;
}
interface CloseEventInit extends EventInit {
code?: number;
reason?: string;
wasClean?: boolean;
}
declare class CloseEvent extends Event {
code: number;
reason: string;
wasClean: boolean;
constructor(type: string, init?: CloseEventInit);
}
declare class CancelableCloseEvent extends CloseEvent {
[kCancelable]: boolean;
[kDefaultPrevented]: boolean;
constructor(type: string, init?: CloseEventInit);
get cancelable(): boolean;
set cancelable(nextCancelable: boolean);
get defaultPrevented(): boolean;
set defaultPrevented(nextDefaultPrevented: boolean);
preventDefault(): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketTransport.d.ts
type WebSocketData = string | ArrayBufferLike | Blob | ArrayBufferView;
type WebSocketTransportEventMap = {
incoming: MessageEvent<WebSocketData>;
outgoing: MessageEvent<WebSocketData>;
close: CloseEvent;
};
type StrictEventListenerOrEventListenerObject<EventType extends Event> = ((this: WebSocket, event: EventType) => void) | {
handleEvent(this: WebSocket, event: EventType): void;
};
interface WebSocketTransport {
addEventListener<EventType extends keyof WebSocketTransportEventMap>(event: EventType, listener: StrictEventListenerOrEventListenerObject<WebSocketTransportEventMap[EventType]> | null, options?: boolean | AddEventListenerOptions): void;
dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(event: WebSocketTransportEventMap[EventType]): boolean;
/**
* Send the data from the server to this client.
*/
send(data: WebSocketData): void;
/**
* Close the client connection.
*/
close(code?: number, reason?: string): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketOverride.d.ts
type WebSocketEventListener<EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event> = (this: WebSocket, event: EventType) => void;
declare const kPassthroughPromise: unique symbol;
declare const kOnSend: unique symbol;
declare const kClose: unique symbol;
declare class WebSocketOverride extends EventTarget implements WebSocket {
static readonly CONNECTING = 0;
static readonly OPEN = 1;
static readonly CLOSING = 2;
static readonly CLOSED = 3;
readonly CONNECTING = 0;
readonly OPEN = 1;
readonly CLOSING = 2;
readonly CLOSED = 3;
url: string;
protocol: string;
extensions: string;
binaryType: BinaryType;
readyState: WebSocket['readyState'];
bufferedAmount: number;
private _onopen;
private _onmessage;
private _onerror;
private _onclose;
private [kPassthroughPromise];
private [kOnSend]?;
constructor(url: string | URL, protocols?: string | Array<string>);
set onopen(listener: WebSocketEventListener | null);
get onopen(): WebSocketEventListener | null;
set onmessage(listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null);
get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null;
set onerror(listener: WebSocketEventListener | null);
get onerror(): WebSocketEventListener | null;
set onclose(listener: WebSocketEventListener<CloseEvent> | null);
get onclose(): WebSocketEventListener<CloseEvent> | null;
/**
* @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0
*/
send(data: WebSocketData): void;
close(code?: number, reason?: string): void;
private [kClose];
addEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, event: WebSocketEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof WebSocketEventMap>(type: K, callback: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketClientConnection.d.ts
declare const kEmitter$1: unique symbol;
interface WebSocketClientEventMap {
message: MessageEvent<WebSocketData>;
close: CloseEvent;
}
declare abstract class WebSocketClientConnectionProtocol {
abstract id: string;
abstract url: URL;
abstract send(data: WebSocketData): void;
abstract close(code?: number, reason?: string): void;
abstract addEventListener<EventType extends keyof WebSocketClientEventMap>(type: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
abstract removeEventListener<EventType extends keyof WebSocketClientEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
}
/**
* The WebSocket client instance represents an incoming
* client connection. The user can control the connection,
* send and receive events.
*/
declare class WebSocketClientConnection implements WebSocketClientConnectionProtocol {
readonly socket: WebSocket;
private readonly transport;
readonly id: string;
readonly url: URL;
private [kEmitter$1];
constructor(socket: WebSocket, transport: WebSocketTransport);
/**
* Listen for the outgoing events from the connected WebSocket client.
*/
addEventListener<EventType extends keyof WebSocketClientEventMap>(type: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
/**
* Removes the listener for the given event.
*/
removeEventListener<EventType extends keyof WebSocketClientEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
/**
* Send data to the connected client.
*/
send(data: WebSocketData): void;
/**
* Close the WebSocket connection.
* @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).
* @param {string} reason A custom connection close reason.
*/
close(code?: number, reason?: string): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketClassTransport.d.ts
/**
* Abstraction over the given mock `WebSocket` instance that allows
* for controlling that instance (e.g. sending and receiving messages).
*/
declare class WebSocketClassTransport extends EventTarget implements WebSocketTransport {
protected readonly socket: WebSocketOverride;
constructor(socket: WebSocketOverride);
addEventListener<EventType extends keyof WebSocketTransportEventMap>(type: EventType, callback: StrictEventListenerOrEventListenerObject<WebSocketTransportEventMap[EventType]> | null, options?: boolean | AddEventListenerOptions): void;
dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(event: WebSocketTransportEventMap[EventType]): boolean;
send(data: WebSocketData): void;
close(code: number, reason?: string): void;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketServerConnection.d.ts
declare const kEmitter: unique symbol;
declare const kSend: unique symbol;
interface WebSocketServerEventMap {
open: Event;
message: MessageEvent<WebSocketData>;
error: Event;
close: CloseEvent;
}
declare abstract class WebSocketServerConnectionProtocol {
abstract connect(): void;
abstract send(data: WebSocketData): void;
abstract close(): void;
abstract addEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
abstract removeEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
}
/**
* The WebSocket server instance represents the actual production
* WebSocket server connection. It's idle by default but you can
* establish it by calling `server.connect()`.
*/
declare class WebSocketServerConnection implements WebSocketServerConnectionProtocol {
private readonly client;
private readonly transport;
private readonly createConnection;
/**
* A WebSocket instance connected to the original server.
*/
private realWebSocket?;
private mockCloseController;
private realCloseController;
private [kEmitter];
constructor(client: WebSocketOverride, transport: WebSocketClassTransport, createConnection: () => WebSocket);
/**
* The `WebSocket` instance connected to the original server.
* Accessing this before calling `server.connect()` will throw.
*/
get socket(): WebSocket;
/**
* Open connection to the original WebSocket server.
*/
connect(): void;
/**
* Listen for the incoming events from the original WebSocket server.
*/
addEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
/**
* Remove the listener for the given event.
*/
removeEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
/**
* Send data to the original WebSocket server.
* @example
* server.send('hello')
* server.send(new Blob(['hello']))
* server.send(new TextEncoder().encode('hello'))
*/
send(data: WebSocketData): void;
private [kSend];
/**
* Close the actual server connection.
*/
close(): void;
private handleIncomingMessage;
private handleMockClose;
private handleRealClose;
}
//#endregion
//#region src/interceptors/WebSocket/index.d.ts
type WebSocketEventMap$1 = {
connection: [args: WebSocketConnectionData];
};
type WebSocketConnectionData = {
/**
* The incoming WebSocket client connection.
*/
client: WebSocketClientConnection;
/**
* The original WebSocket server connection.
*/
server: WebSocketServerConnection;
/**
* The connection information.
*/
info: {
/**
* The protocols supported by the WebSocket client.
*/
protocols: string | Array<string> | undefined;
};
};
/**
* Intercept the outgoing WebSocket connections created using
* the global `WebSocket` class.
*/
declare class WebSocketInterceptor extends Interceptor<WebSocketEventMap$1> {
static symbol: symbol;
constructor();
protected checkEnvironment(): boolean;
protected setup(): void;
}
//#endregion
export { CancelableCloseEvent, CancelableMessageEvent, CloseEvent, WebSocketClientConnection, WebSocketClientConnectionProtocol, WebSocketClientEventMap, WebSocketConnectionData, type WebSocketData, WebSocketEventMap$1 as WebSocketEventMap, WebSocketInterceptor, WebSocketServerConnection, WebSocketServerConnectionProtocol, WebSocketServerEventMap, type WebSocketTransport };
//# sourceMappingURL=index.d.mts.map

View File

@@ -0,0 +1,615 @@
import { r as Interceptor, t as createRequestId } from "../../createRequestId-DQcIlohW.mjs";
import { t as resolveWebSocketUrl } from "../../resolveWebSocketUrl-C83-x9iE.mjs";
import { n as emitAsync, t as hasConfigurableGlobal } from "../../hasConfigurableGlobal-npXitu1-.mjs";
import { DeferredPromise } from "@open-draft/deferred-promise";
import { invariant } from "outvariant";
//#region src/interceptors/WebSocket/utils/bindEvent.ts
function bindEvent(target, event) {
Object.defineProperties(event, {
target: {
value: target,
enumerable: true,
writable: true
},
currentTarget: {
value: target,
enumerable: true,
writable: true
}
});
return event;
}
//#endregion
//#region src/interceptors/WebSocket/utils/events.ts
const kCancelable = Symbol("kCancelable");
const kDefaultPrevented = Symbol("kDefaultPrevented");
/**
* A `MessageEvent` superset that supports event cancellation
* in Node.js. It's rather non-intrusive so it can be safely
* used in the browser as well.
*
* @see https://github.com/nodejs/node/issues/51767
*/
var CancelableMessageEvent = class extends MessageEvent {
constructor(type, init) {
super(type, init);
this[kCancelable] = !!init.cancelable;
this[kDefaultPrevented] = false;
}
get cancelable() {
return this[kCancelable];
}
set cancelable(nextCancelable) {
this[kCancelable] = nextCancelable;
}
get defaultPrevented() {
return this[kDefaultPrevented];
}
set defaultPrevented(nextDefaultPrevented) {
this[kDefaultPrevented] = nextDefaultPrevented;
}
preventDefault() {
if (this.cancelable && !this[kDefaultPrevented]) this[kDefaultPrevented] = true;
}
};
var CloseEvent = class extends Event {
constructor(type, init = {}) {
super(type, init);
this.code = init.code === void 0 ? 0 : init.code;
this.reason = init.reason === void 0 ? "" : init.reason;
this.wasClean = init.wasClean === void 0 ? false : init.wasClean;
}
};
var CancelableCloseEvent = class extends CloseEvent {
constructor(type, init = {}) {
super(type, init);
this[kCancelable] = !!init.cancelable;
this[kDefaultPrevented] = false;
}
get cancelable() {
return this[kCancelable];
}
set cancelable(nextCancelable) {
this[kCancelable] = nextCancelable;
}
get defaultPrevented() {
return this[kDefaultPrevented];
}
set defaultPrevented(nextDefaultPrevented) {
this[kDefaultPrevented] = nextDefaultPrevented;
}
preventDefault() {
if (this.cancelable && !this[kDefaultPrevented]) this[kDefaultPrevented] = true;
}
};
//#endregion
//#region src/interceptors/WebSocket/WebSocketClientConnection.ts
const kEmitter$1 = Symbol("kEmitter");
const kBoundListener$1 = Symbol("kBoundListener");
var WebSocketClientConnectionProtocol = class {};
/**
* The WebSocket client instance represents an incoming
* client connection. The user can control the connection,
* send and receive events.
*/
var WebSocketClientConnection = class {
constructor(socket, transport) {
this.socket = socket;
this.transport = transport;
this.id = createRequestId();
this.url = new URL(socket.url);
this[kEmitter$1] = new EventTarget();
this.transport.addEventListener("outgoing", (event) => {
const message = bindEvent(this.socket, new CancelableMessageEvent("message", {
data: event.data,
origin: event.origin,
cancelable: true
}));
this[kEmitter$1].dispatchEvent(message);
if (message.defaultPrevented) event.preventDefault();
});
/**
* Emit the "close" event on the "client" connection
* whenever the underlying transport is closed.
* @note "client.close()" does NOT dispatch the "close"
* event on the WebSocket because it uses non-configurable
* close status code. Thus, we listen to the transport
* instead of the WebSocket's "close" event.
*/
this.transport.addEventListener("close", (event) => {
this[kEmitter$1].dispatchEvent(bindEvent(this.socket, new CloseEvent("close", event)));
});
}
/**
* Listen for the outgoing events from the connected WebSocket client.
*/
addEventListener(type, listener, options) {
if (!Reflect.has(listener, kBoundListener$1)) {
const boundListener = listener.bind(this.socket);
Object.defineProperty(listener, kBoundListener$1, {
value: boundListener,
enumerable: false,
configurable: false
});
}
this[kEmitter$1].addEventListener(type, Reflect.get(listener, kBoundListener$1), options);
}
/**
* Removes the listener for the given event.
*/
removeEventListener(event, listener, options) {
this[kEmitter$1].removeEventListener(event, Reflect.get(listener, kBoundListener$1), options);
}
/**
* Send data to the connected client.
*/
send(data) {
this.transport.send(data);
}
/**
* Close the WebSocket connection.
* @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).
* @param {string} reason A custom connection close reason.
*/
close(code, reason) {
this.transport.close(code, reason);
}
};
//#endregion
//#region src/interceptors/WebSocket/WebSocketOverride.ts
const WEBSOCKET_CLOSE_CODE_RANGE_ERROR = "InvalidAccessError: close code out of user configurable range";
const kPassthroughPromise = Symbol("kPassthroughPromise");
const kOnSend = Symbol("kOnSend");
const kClose = Symbol("kClose");
var WebSocketOverride = class extends EventTarget {
static {
this.CONNECTING = 0;
}
static {
this.OPEN = 1;
}
static {
this.CLOSING = 2;
}
static {
this.CLOSED = 3;
}
constructor(url, protocols) {
super();
this.CONNECTING = 0;
this.OPEN = 1;
this.CLOSING = 2;
this.CLOSED = 3;
this._onopen = null;
this._onmessage = null;
this._onerror = null;
this._onclose = null;
this.url = resolveWebSocketUrl(url);
this.protocol = "";
this.extensions = "";
this.binaryType = "blob";
this.readyState = this.CONNECTING;
this.bufferedAmount = 0;
this[kPassthroughPromise] = new DeferredPromise();
queueMicrotask(async () => {
if (await this[kPassthroughPromise]) return;
this.protocol = typeof protocols === "string" ? protocols : Array.isArray(protocols) && protocols.length > 0 ? protocols[0] : "";
/**
* @note Check that nothing has prevented this connection
* (e.g. called `client.close()` in the connection listener).
* If the connection has been prevented, never dispatch the open event,.
*/
if (this.readyState === this.CONNECTING) {
this.readyState = this.OPEN;
this.dispatchEvent(bindEvent(this, new Event("open")));
}
});
}
set onopen(listener) {
this.removeEventListener("open", this._onopen);
this._onopen = listener;
if (listener !== null) this.addEventListener("open", listener);
}
get onopen() {
return this._onopen;
}
set onmessage(listener) {
this.removeEventListener("message", this._onmessage);
this._onmessage = listener;
if (listener !== null) this.addEventListener("message", listener);
}
get onmessage() {
return this._onmessage;
}
set onerror(listener) {
this.removeEventListener("error", this._onerror);
this._onerror = listener;
if (listener !== null) this.addEventListener("error", listener);
}
get onerror() {
return this._onerror;
}
set onclose(listener) {
this.removeEventListener("close", this._onclose);
this._onclose = listener;
if (listener !== null) this.addEventListener("close", listener);
}
get onclose() {
return this._onclose;
}
/**
* @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0
*/
send(data) {
if (this.readyState === this.CONNECTING) {
this.close();
throw new DOMException("InvalidStateError");
}
if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) return;
this.bufferedAmount += getDataSize(data);
queueMicrotask(() => {
this.bufferedAmount = 0;
/**
* @note Notify the parent about outgoing data.
* This notifies the transport and the connection
* listens to the outgoing data to emit the "message" event.
*/
this[kOnSend]?.(data);
});
}
close(code = 1e3, reason) {
invariant(code, WEBSOCKET_CLOSE_CODE_RANGE_ERROR);
invariant(code === 1e3 || code >= 3e3 && code <= 4999, WEBSOCKET_CLOSE_CODE_RANGE_ERROR);
this[kClose](code, reason);
}
[kClose](code = 1e3, reason, wasClean = true) {
/**
* @note Move this check here so that even internal closures,
* like those triggered by the `server` connection, are not
* performed twice.
*/
if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) return;
this.readyState = this.CLOSING;
queueMicrotask(() => {
this.readyState = this.CLOSED;
this.dispatchEvent(bindEvent(this, new CloseEvent("close", {
code,
reason,
wasClean
})));
this._onopen = null;
this._onmessage = null;
this._onerror = null;
this._onclose = null;
});
}
addEventListener(type, listener, options) {
return super.addEventListener(type, listener, options);
}
removeEventListener(type, callback, options) {
return super.removeEventListener(type, callback, options);
}
};
function getDataSize(data) {
if (typeof data === "string") return data.length;
if (data instanceof Blob) return data.size;
return data.byteLength;
}
//#endregion
//#region src/interceptors/WebSocket/WebSocketServerConnection.ts
const kEmitter = Symbol("kEmitter");
const kBoundListener = Symbol("kBoundListener");
const kSend = Symbol("kSend");
var WebSocketServerConnectionProtocol = class {};
/**
* The WebSocket server instance represents the actual production
* WebSocket server connection. It's idle by default but you can
* establish it by calling `server.connect()`.
*/
var WebSocketServerConnection = class {
constructor(client, transport, createConnection) {
this.client = client;
this.transport = transport;
this.createConnection = createConnection;
this[kEmitter] = new EventTarget();
this.mockCloseController = new AbortController();
this.realCloseController = new AbortController();
this.transport.addEventListener("outgoing", (event) => {
if (typeof this.realWebSocket === "undefined") return;
queueMicrotask(() => {
if (!event.defaultPrevented)
/**
* @note Use the internal send mechanism so consumers can tell
* apart direct user calls to `server.send()` and internal calls.
* E.g. MSW has to ignore this internal call to log out messages correctly.
*/
this[kSend](event.data);
});
});
this.transport.addEventListener("incoming", this.handleIncomingMessage.bind(this));
}
/**
* The `WebSocket` instance connected to the original server.
* Accessing this before calling `server.connect()` will throw.
*/
get socket() {
invariant(this.realWebSocket, "Cannot access \"socket\" on the original WebSocket server object: the connection is not open. Did you forget to call `server.connect()`?");
return this.realWebSocket;
}
/**
* Open connection to the original WebSocket server.
*/
connect() {
invariant(!this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN, "Failed to call \"connect()\" on the original WebSocket instance: the connection already open");
const realWebSocket = this.createConnection();
realWebSocket.binaryType = this.client.binaryType;
realWebSocket.addEventListener("open", (event) => {
this[kEmitter].dispatchEvent(bindEvent(this.realWebSocket, new Event("open", event)));
}, { once: true });
realWebSocket.addEventListener("message", (event) => {
this.transport.dispatchEvent(bindEvent(this.realWebSocket, new MessageEvent("incoming", {
data: event.data,
origin: event.origin
})));
});
this.client.addEventListener("close", (event) => {
this.handleMockClose(event);
}, { signal: this.mockCloseController.signal });
realWebSocket.addEventListener("close", (event) => {
this.handleRealClose(event);
}, { signal: this.realCloseController.signal });
realWebSocket.addEventListener("error", () => {
const errorEvent = bindEvent(realWebSocket, new Event("error", { cancelable: true }));
this[kEmitter].dispatchEvent(errorEvent);
if (!errorEvent.defaultPrevented) this.client.dispatchEvent(bindEvent(this.client, new Event("error")));
});
this.realWebSocket = realWebSocket;
}
/**
* Listen for the incoming events from the original WebSocket server.
*/
addEventListener(event, listener, options) {
if (!Reflect.has(listener, kBoundListener)) {
const boundListener = listener.bind(this.client);
Object.defineProperty(listener, kBoundListener, {
value: boundListener,
enumerable: false
});
}
this[kEmitter].addEventListener(event, Reflect.get(listener, kBoundListener), options);
}
/**
* Remove the listener for the given event.
*/
removeEventListener(event, listener, options) {
this[kEmitter].removeEventListener(event, Reflect.get(listener, kBoundListener), options);
}
/**
* Send data to the original WebSocket server.
* @example
* server.send('hello')
* server.send(new Blob(['hello']))
* server.send(new TextEncoder().encode('hello'))
*/
send(data) {
this[kSend](data);
}
[kSend](data) {
const { realWebSocket } = this;
invariant(realWebSocket, "Failed to call \"server.send()\" for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?", this.client.url);
if (realWebSocket.readyState === WebSocket.CLOSING || realWebSocket.readyState === WebSocket.CLOSED) return;
if (realWebSocket.readyState === WebSocket.CONNECTING) {
realWebSocket.addEventListener("open", () => {
realWebSocket.send(data);
}, { once: true });
return;
}
realWebSocket.send(data);
}
/**
* Close the actual server connection.
*/
close() {
const { realWebSocket } = this;
invariant(realWebSocket, "Failed to close server connection for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?", this.client.url);
this.realCloseController.abort();
if (realWebSocket.readyState === WebSocket.CLOSING || realWebSocket.readyState === WebSocket.CLOSED) return;
realWebSocket.close();
queueMicrotask(() => {
this[kEmitter].dispatchEvent(bindEvent(this.realWebSocket, new CancelableCloseEvent("close", {
code: 1e3,
cancelable: true
})));
});
}
handleIncomingMessage(event) {
const messageEvent = bindEvent(event.target, new CancelableMessageEvent("message", {
data: event.data,
origin: event.origin,
cancelable: true
}));
/**
* @note Emit "message" event on the server connection
* instance to let the interceptor know about these
* incoming events from the original server. In that listener,
* the interceptor can modify or skip the event forwarding
* to the mock WebSocket instance.
*/
this[kEmitter].dispatchEvent(messageEvent);
/**
* @note Forward the incoming server events to the client.
* Preventing the default on the message event stops this.
*/
if (!messageEvent.defaultPrevented) this.client.dispatchEvent(bindEvent(
/**
* @note Bind the forwarded original server events
* to the mock WebSocket instance so it would
* dispatch them straight away.
*/
this.client,
new MessageEvent("message", {
data: event.data,
origin: event.origin
})
));
}
handleMockClose(_event) {
if (this.realWebSocket) this.realWebSocket.close();
}
handleRealClose(event) {
this.mockCloseController.abort();
const closeEvent = bindEvent(this.realWebSocket, new CancelableCloseEvent("close", {
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
cancelable: true
}));
this[kEmitter].dispatchEvent(closeEvent);
if (!closeEvent.defaultPrevented) this.client[kClose](event.code, event.reason);
}
};
//#endregion
//#region src/interceptors/WebSocket/WebSocketClassTransport.ts
/**
* Abstraction over the given mock `WebSocket` instance that allows
* for controlling that instance (e.g. sending and receiving messages).
*/
var WebSocketClassTransport = class extends EventTarget {
constructor(socket) {
super();
this.socket = socket;
this.socket.addEventListener("close", (event) => {
this.dispatchEvent(bindEvent(this.socket, new CloseEvent("close", event)));
});
/**
* Emit the "outgoing" event on the transport
* whenever the WebSocket client sends data ("ws.send()").
*/
this.socket[kOnSend] = (data) => {
this.dispatchEvent(bindEvent(this.socket, new CancelableMessageEvent("outgoing", {
data,
origin: this.socket.url,
cancelable: true
})));
};
}
addEventListener(type, callback, options) {
return super.addEventListener(type, callback, options);
}
dispatchEvent(event) {
return super.dispatchEvent(event);
}
send(data) {
queueMicrotask(() => {
if (this.socket.readyState === this.socket.CLOSING || this.socket.readyState === this.socket.CLOSED) return;
const dispatchEvent = () => {
this.socket.dispatchEvent(bindEvent(
/**
* @note Setting this event's "target" to the
* WebSocket override instance is important.
* This way it can tell apart original incoming events
* (must be forwarded to the transport) from the
* mocked message events like the one below
* (must be dispatched on the client instance).
*/
this.socket,
new MessageEvent("message", {
data,
origin: this.socket.url
})
));
};
if (this.socket.readyState === this.socket.CONNECTING) this.socket.addEventListener("open", () => {
dispatchEvent();
}, { once: true });
else dispatchEvent();
});
}
close(code, reason) {
/**
* @note Call the internal close method directly
* to allow closing the connection with the status codes
* that are non-configurable by the user (> 1000 <= 1015).
*/
this.socket[kClose](code, reason);
}
};
//#endregion
//#region src/interceptors/WebSocket/index.ts
/**
* Intercept the outgoing WebSocket connections created using
* the global `WebSocket` class.
*/
var WebSocketInterceptor = class WebSocketInterceptor extends Interceptor {
static {
this.symbol = Symbol("websocket");
}
constructor() {
super(WebSocketInterceptor.symbol);
}
checkEnvironment() {
return hasConfigurableGlobal("WebSocket");
}
setup() {
const originalWebSocketDescriptor = Object.getOwnPropertyDescriptor(globalThis, "WebSocket");
const WebSocketProxy = new Proxy(globalThis.WebSocket, { construct: (target, args, newTarget) => {
const [url, protocols] = args;
const createConnection = () => {
return Reflect.construct(target, args, newTarget);
};
const socket = new WebSocketOverride(url, protocols);
const transport = new WebSocketClassTransport(socket);
queueMicrotask(async () => {
try {
const server = new WebSocketServerConnection(socket, transport, createConnection);
const hasConnectionListeners = this.emitter.listenerCount("connection") > 0;
await emitAsync(this.emitter, "connection", {
client: new WebSocketClientConnection(socket, transport),
server,
info: { protocols }
});
if (hasConnectionListeners) socket[kPassthroughPromise].resolve(false);
else {
socket[kPassthroughPromise].resolve(true);
server.connect();
server.addEventListener("open", () => {
socket.dispatchEvent(bindEvent(socket, new Event("open")));
if (server["realWebSocket"]) socket.protocol = server["realWebSocket"].protocol;
});
}
} catch (error) {
/**
* @note Translate unhandled exceptions during the connection
* handling (i.e. interceptor exceptions) as WebSocket connection
* closures with error. This prevents from the exceptions occurring
* in `queueMicrotask` from being process-wide and uncatchable.
*/
if (error instanceof Error) {
socket.dispatchEvent(new Event("error"));
if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) socket[kClose](1011, error.message, false);
console.error(error);
}
}
});
return socket;
} });
Object.defineProperty(globalThis, "WebSocket", {
value: WebSocketProxy,
configurable: true
});
this.subscriptions.push(() => {
Object.defineProperty(globalThis, "WebSocket", originalWebSocketDescriptor);
});
}
};
//#endregion
export { CancelableCloseEvent, CancelableMessageEvent, CloseEvent, WebSocketClientConnection, WebSocketClientConnectionProtocol, WebSocketInterceptor, WebSocketServerConnection, WebSocketServerConnectionProtocol };
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
require('../../getRawRequest-zx8rUJL2.cjs');
require('../../createRequestId-Cs4oXfa1.cjs');
require('../../bufferUtils-Uc0eRItL.cjs');
require('../../handleRequest-CvX2G-Lz.cjs');
const require_XMLHttpRequest = require('../../XMLHttpRequest-BACqefB-.cjs');
exports.XMLHttpRequestInterceptor = require_XMLHttpRequest.XMLHttpRequestInterceptor;

View File

@@ -0,0 +1,15 @@
import { t as HttpRequestEventMap } from "../../glossary-BdLS4k1H.cjs";
import { r as Interceptor } from "../../Interceptor-Deczogc8.cjs";
import { Emitter } from "strict-event-emitter";
//#region src/interceptors/XMLHttpRequest/index.d.ts
type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>;
declare class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {
static interceptorSymbol: symbol;
constructor();
protected checkEnvironment(): boolean;
protected setup(): void;
}
//#endregion
export { XMLHttpRequestEmitter, XMLHttpRequestInterceptor };
//# sourceMappingURL=index.d.cts.map

View File

@@ -0,0 +1,15 @@
import { t as HttpRequestEventMap } from "../../glossary-DYwOrogs.mjs";
import { r as Interceptor } from "../../Interceptor-gqKgs-aF.mjs";
import { Emitter } from "strict-event-emitter";
//#region src/interceptors/XMLHttpRequest/index.d.ts
type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>;
declare class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {
static interceptorSymbol: symbol;
constructor();
protected checkEnvironment(): boolean;
protected setup(): void;
}
//#endregion
export { XMLHttpRequestEmitter, XMLHttpRequestInterceptor };
//# sourceMappingURL=index.d.mts.map

View File

@@ -0,0 +1,7 @@
import "../../getRawRequest-BTaNLFr0.mjs";
import "../../createRequestId-DQcIlohW.mjs";
import "../../bufferUtils-BiiO6HZv.mjs";
import "../../handleRequest-D7kpTI5U.mjs";
import { t as XMLHttpRequestInterceptor } from "../../XMLHttpRequest-BvxZV0WU.mjs";
export { XMLHttpRequestInterceptor };

View File

@@ -0,0 +1,6 @@
require('../../getRawRequest-zx8rUJL2.cjs');
require('../../createRequestId-Cs4oXfa1.cjs');
require('../../handleRequest-CvX2G-Lz.cjs');
const require_fetch = require('../../fetch-U3v3Y4ap.cjs');
exports.FetchInterceptor = require_fetch.FetchInterceptor;

View File

@@ -0,0 +1,13 @@
import { t as HttpRequestEventMap } from "../../glossary-BdLS4k1H.cjs";
import { r as Interceptor } from "../../Interceptor-Deczogc8.cjs";
//#region src/interceptors/fetch/index.d.ts
declare class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
static symbol: symbol;
constructor();
protected checkEnvironment(): boolean;
protected setup(): Promise<void>;
}
//#endregion
export { FetchInterceptor };
//# sourceMappingURL=index.d.cts.map

View File

@@ -0,0 +1,13 @@
import { t as HttpRequestEventMap } from "../../glossary-DYwOrogs.mjs";
import { r as Interceptor } from "../../Interceptor-gqKgs-aF.mjs";
//#region src/interceptors/fetch/index.d.ts
declare class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
static symbol: symbol;
constructor();
protected checkEnvironment(): boolean;
protected setup(): Promise<void>;
}
//#endregion
export { FetchInterceptor };
//# sourceMappingURL=index.d.mts.map

View File

@@ -0,0 +1,6 @@
import "../../getRawRequest-BTaNLFr0.mjs";
import "../../createRequestId-DQcIlohW.mjs";
import "../../handleRequest-D7kpTI5U.mjs";
import { t as FetchInterceptor } from "../../fetch-DdKEdDOR.mjs";
export { FetchInterceptor };

View File

@@ -0,0 +1,17 @@
require('../getRawRequest-zx8rUJL2.cjs');
require('../createRequestId-Cs4oXfa1.cjs');
require('../bufferUtils-Uc0eRItL.cjs');
require('../handleRequest-CvX2G-Lz.cjs');
const require_fetch = require('../fetch-U3v3Y4ap.cjs');
const require_XMLHttpRequest = require('../XMLHttpRequest-BACqefB-.cjs');
//#region src/presets/browser.ts
/**
* The default preset provisions the interception of requests
* regardless of their type (fetch/XMLHttpRequest).
*/
var browser_default = [new require_fetch.FetchInterceptor(), new require_XMLHttpRequest.XMLHttpRequestInterceptor()];
//#endregion
module.exports = browser_default;
//# sourceMappingURL=browser.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"browser.cjs","names":["FetchInterceptor","XMLHttpRequestInterceptor"],"sources":["../../../src/presets/browser.ts"],"sourcesContent":["import { FetchInterceptor } from '../interceptors/fetch'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (fetch/XMLHttpRequest).\n */\nexport default [\n new FetchInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"],"mappings":";;;;;;;;;;;;AAOA,sBAAe,CACb,IAAIA,gCAAkB,EACtB,IAAIC,kDAA2B,CAChC"}

View File

@@ -0,0 +1,12 @@
import { XMLHttpRequestInterceptor } from "../interceptors/XMLHttpRequest/index.cjs";
import { FetchInterceptor } from "../interceptors/fetch/index.cjs";
//#region src/presets/browser.d.ts
/**
* The default preset provisions the interception of requests
* regardless of their type (fetch/XMLHttpRequest).
*/
declare const _default: readonly [FetchInterceptor, XMLHttpRequestInterceptor];
export = _default;
//# sourceMappingURL=browser.d.cts.map

View File

@@ -0,0 +1,14 @@
import "../Interceptor-gqKgs-aF.mjs";
import { XMLHttpRequestInterceptor } from "../interceptors/XMLHttpRequest/index.mjs";
import { FetchInterceptor } from "../interceptors/fetch/index.mjs";
//#region src/presets/browser.d.ts
/**
* The default preset provisions the interception of requests
* regardless of their type (fetch/XMLHttpRequest).
*/
declare const _default: readonly [FetchInterceptor, XMLHttpRequestInterceptor];
//#endregion
export { _default as default };
//# sourceMappingURL=browser.d.mts.map

View File

@@ -0,0 +1,17 @@
import "../getRawRequest-BTaNLFr0.mjs";
import "../createRequestId-DQcIlohW.mjs";
import "../bufferUtils-BiiO6HZv.mjs";
import "../handleRequest-D7kpTI5U.mjs";
import { t as FetchInterceptor } from "../fetch-DdKEdDOR.mjs";
import { t as XMLHttpRequestInterceptor } from "../XMLHttpRequest-BvxZV0WU.mjs";
//#region src/presets/browser.ts
/**
* The default preset provisions the interception of requests
* regardless of their type (fetch/XMLHttpRequest).
*/
var browser_default = [new FetchInterceptor(), new XMLHttpRequestInterceptor()];
//#endregion
export { browser_default as default };
//# sourceMappingURL=browser.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"browser.mjs","names":[],"sources":["../../../src/presets/browser.ts"],"sourcesContent":["import { FetchInterceptor } from '../interceptors/fetch'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (fetch/XMLHttpRequest).\n */\nexport default [\n new FetchInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"],"mappings":";;;;;;;;;;;;AAOA,sBAAe,CACb,IAAI,kBAAkB,EACtB,IAAI,2BAA2B,CAChC"}

View File

@@ -0,0 +1,31 @@
//#region src/utils/resolveWebSocketUrl.ts
/**
* Resolve potentially relative WebSocket URLs the same way
* the browser does (replace the protocol, use the origin, etc).
*
* @see https://websockets.spec.whatwg.org//#dom-websocket-websocket
*/
function resolveWebSocketUrl(url) {
if (typeof url === "string") return resolveWebSocketUrl(new URL(url, typeof location !== "undefined" ? location.href : void 0));
if (url.protocol === "http:") url.protocol = "ws:";
else if (url.protocol === "https:") url.protocol = "wss:";
if (url.protocol !== "ws:" && url.protocol !== "wss:")
/**
* @note These errors are modeled after the browser errors.
* The exact error messages aren't provided in the specification.
* Node.js uses more obscure error messages that I don't wish to replicate.
*/
throw new SyntaxError(`Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '${url.protocol}' is not allowed.`);
if (url.hash !== "") throw new SyntaxError(`Failed to construct 'WebSocket': The URL contains a fragment identifier ('${url.hash}'). Fragment identifiers are not allowed in WebSocket URLs.`);
return url.href;
}
//#endregion
Object.defineProperty(exports, 'resolveWebSocketUrl', {
enumerable: true,
get: function () {
return resolveWebSocketUrl;
}
});
//# sourceMappingURL=resolveWebSocketUrl-6K6EgqsA.cjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveWebSocketUrl-6K6EgqsA.cjs","names":[],"sources":["../../src/utils/resolveWebSocketUrl.ts"],"sourcesContent":["/**\n * Resolve potentially relative WebSocket URLs the same way\n * the browser does (replace the protocol, use the origin, etc).\n *\n * @see https://websockets.spec.whatwg.org//#dom-websocket-websocket\n */\nexport function resolveWebSocketUrl(url: string | URL): string {\n if (typeof url === 'string') {\n /**\n * @note Cast the string to a URL first so the parsing errors\n * are thrown as a part of the WebSocket constructor, not consumers.\n */\n const urlRecord = new URL(\n url,\n typeof location !== 'undefined' ? location.href : undefined\n )\n\n return resolveWebSocketUrl(urlRecord)\n }\n\n if (url.protocol === 'http:') {\n url.protocol = 'ws:'\n } else if (url.protocol === 'https:') {\n url.protocol = 'wss:'\n }\n\n if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {\n /**\n * @note These errors are modeled after the browser errors.\n * The exact error messages aren't provided in the specification.\n * Node.js uses more obscure error messages that I don't wish to replicate.\n */\n throw new SyntaxError(\n `Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '${url.protocol}' is not allowed.`\n )\n }\n\n if (url.hash !== '') {\n throw new SyntaxError(\n `Failed to construct 'WebSocket': The URL contains a fragment identifier ('${url.hash}'). Fragment identifiers are not allowed in WebSocket URLs.`\n )\n }\n\n return url.href\n}\n"],"mappings":";;;;;;;;AAMA,SAAgB,oBAAoB,KAA2B;AAC7D,KAAI,OAAO,QAAQ,SAUjB,QAAO,oBALW,IAAI,IACpB,KACA,OAAO,aAAa,cAAc,SAAS,OAAO,OACnD,CAEoC;AAGvC,KAAI,IAAI,aAAa,QACnB,KAAI,WAAW;UACN,IAAI,aAAa,SAC1B,KAAI,WAAW;AAGjB,KAAI,IAAI,aAAa,SAAS,IAAI,aAAa;;;;;;AAM7C,OAAM,IAAI,YACR,sGAAsG,IAAI,SAAS,mBACpH;AAGH,KAAI,IAAI,SAAS,GACf,OAAM,IAAI,YACR,6EAA6E,IAAI,KAAK,6DACvF;AAGH,QAAO,IAAI"}

View File

@@ -0,0 +1,25 @@
//#region src/utils/resolveWebSocketUrl.ts
/**
* Resolve potentially relative WebSocket URLs the same way
* the browser does (replace the protocol, use the origin, etc).
*
* @see https://websockets.spec.whatwg.org//#dom-websocket-websocket
*/
function resolveWebSocketUrl(url) {
if (typeof url === "string") return resolveWebSocketUrl(new URL(url, typeof location !== "undefined" ? location.href : void 0));
if (url.protocol === "http:") url.protocol = "ws:";
else if (url.protocol === "https:") url.protocol = "wss:";
if (url.protocol !== "ws:" && url.protocol !== "wss:")
/**
* @note These errors are modeled after the browser errors.
* The exact error messages aren't provided in the specification.
* Node.js uses more obscure error messages that I don't wish to replicate.
*/
throw new SyntaxError(`Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '${url.protocol}' is not allowed.`);
if (url.hash !== "") throw new SyntaxError(`Failed to construct 'WebSocket': The URL contains a fragment identifier ('${url.hash}'). Fragment identifiers are not allowed in WebSocket URLs.`);
return url.href;
}
//#endregion
export { resolveWebSocketUrl as t };
//# sourceMappingURL=resolveWebSocketUrl-C83-x9iE.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveWebSocketUrl-C83-x9iE.mjs","names":[],"sources":["../../src/utils/resolveWebSocketUrl.ts"],"sourcesContent":["/**\n * Resolve potentially relative WebSocket URLs the same way\n * the browser does (replace the protocol, use the origin, etc).\n *\n * @see https://websockets.spec.whatwg.org//#dom-websocket-websocket\n */\nexport function resolveWebSocketUrl(url: string | URL): string {\n if (typeof url === 'string') {\n /**\n * @note Cast the string to a URL first so the parsing errors\n * are thrown as a part of the WebSocket constructor, not consumers.\n */\n const urlRecord = new URL(\n url,\n typeof location !== 'undefined' ? location.href : undefined\n )\n\n return resolveWebSocketUrl(urlRecord)\n }\n\n if (url.protocol === 'http:') {\n url.protocol = 'ws:'\n } else if (url.protocol === 'https:') {\n url.protocol = 'wss:'\n }\n\n if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {\n /**\n * @note These errors are modeled after the browser errors.\n * The exact error messages aren't provided in the specification.\n * Node.js uses more obscure error messages that I don't wish to replicate.\n */\n throw new SyntaxError(\n `Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '${url.protocol}' is not allowed.`\n )\n }\n\n if (url.hash !== '') {\n throw new SyntaxError(\n `Failed to construct 'WebSocket': The URL contains a fragment identifier ('${url.hash}'). Fragment identifiers are not allowed in WebSocket URLs.`\n )\n }\n\n return url.href\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,oBAAoB,KAA2B;AAC7D,KAAI,OAAO,QAAQ,SAUjB,QAAO,oBALW,IAAI,IACpB,KACA,OAAO,aAAa,cAAc,SAAS,OAAO,OACnD,CAEoC;AAGvC,KAAI,IAAI,aAAa,QACnB,KAAI,WAAW;UACN,IAAI,aAAa,SAC1B,KAAI,WAAW;AAGjB,KAAI,IAAI,aAAa,SAAS,IAAI,aAAa;;;;;;AAM7C,OAAM,IAAI,YACR,sGAAsG,IAAI,SAAS,mBACpH;AAGH,KAAI,IAAI,SAAS,GACf,OAAM,IAAI,YACR,6EAA6E,IAAI,KAAK,6DACvF;AAGH,QAAO,IAAI"}