"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const XMLHttpRequestEventTarget_js_1 = __importDefault(require("./XMLHttpRequestEventTarget.cjs")); const PropertySymbol = __importStar(require("../PropertySymbol.cjs")); const XMLHttpRequestReadyStateEnum_js_1 = __importDefault(require("./XMLHttpRequestReadyStateEnum.cjs")); const Event_js_1 = __importDefault(require("../event/Event.cjs")); const DOMException_js_1 = __importDefault(require("../exception/DOMException.cjs")); const DOMExceptionNameEnum_js_1 = __importDefault(require("../exception/DOMExceptionNameEnum.cjs")); const XMLHttpResponseTypeEnum_js_1 = __importDefault(require("./XMLHttpResponseTypeEnum.cjs")); const ErrorEvent_js_1 = __importDefault(require("../event/events/ErrorEvent.cjs")); const Headers_js_1 = __importDefault(require("../fetch/Headers.cjs")); const Fetch_js_1 = __importDefault(require("../fetch/Fetch.cjs")); const SyncFetch_js_1 = __importDefault(require("../fetch/SyncFetch.cjs")); const ProgressEvent_js_1 = __importDefault(require("../event/events/ProgressEvent.cjs")); const NodeTypeEnum_js_1 = __importDefault(require("../nodes/node/NodeTypeEnum.cjs")); const XMLHttpRequestResponseDataParser_js_1 = __importDefault(require("./XMLHttpRequestResponseDataParser.cjs")); const FetchRequestHeaderUtility_js_1 = __importDefault(require("../fetch/utilities/FetchRequestHeaderUtility.cjs")); const WindowBrowserContext_js_1 = __importDefault(require("../window/WindowBrowserContext.cjs")); /** * XMLHttpRequest. * * Based on: * https://github.com/mjwwit/node-XMLHttpRequest/blob/master/lib/XMLHttpRequest.js */ class XMLHttpRequest extends XMLHttpRequestEventTarget_js_1.default { // Constants static UNSENT = XMLHttpRequestReadyStateEnum_js_1.default.unsent; static OPENED = XMLHttpRequestReadyStateEnum_js_1.default.opened; static HEADERS_RECEIVED = XMLHttpRequestReadyStateEnum_js_1.default.headersRecieved; static LOADING = XMLHttpRequestReadyStateEnum_js_1.default.loading; static DONE = XMLHttpRequestReadyStateEnum_js_1.default.done; // Public properties upload = new this[PropertySymbol.window].XMLHttpRequestUpload(); withCredentials = false; // Private properties #async = true; #abortController = null; #aborted = false; #request = null; #response = null; #responseType = ''; #responseBody = null; #readyState = XMLHttpRequestReadyStateEnum_js_1.default.unsent; /** * Constructor. */ constructor() { super(); if (!this[PropertySymbol.window]) { throw new TypeError(`Failed to construct '${this.constructor.name}': '${this.constructor.name}' was constructed outside a Window context.`); } } /** * Returns the status. * * @returns Status. */ get status() { return this.#response?.status || 0; } /** * Returns the status text. * * @returns Status text. */ get statusText() { return this.#response?.statusText || ''; } /** * Returns the response. * * @returns Response. */ get response() { if (!this.#response) { return ''; } return this.#responseBody; } /** * Get the response text. * * @throws {DOMException} If the response type is not text or empty. * @returns The response text. */ get responseText() { if (this.responseType !== XMLHttpResponseTypeEnum_js_1.default.text && this.responseType !== '') { throw new this[PropertySymbol.window].DOMException(`Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '${this.responseType}').`, DOMExceptionNameEnum_js_1.default.invalidStateError); } return this.#responseBody ?? ''; } /** * Get the responseXML. * * @throws {DOMException} If the response type is not text or empty. * @returns Response XML. */ get responseXML() { if (this.responseType !== XMLHttpResponseTypeEnum_js_1.default.document && this.responseType !== '') { throw new this[PropertySymbol.window].DOMException(`Failed to read the 'responseXML' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'document' (was '${this.responseType}').`, DOMExceptionNameEnum_js_1.default.invalidStateError); } return this.responseType === '' ? null : this.#responseBody; } /** * Returns the response URL. * * @returns Response URL. */ get responseURL() { return this.#response?.url || ''; } /** * Returns the ready state. * * @returns Ready state. */ get readyState() { return this.#readyState; } /** * Set response type. * * @param type Response type. * @throws {DOMException} If the state is not unsent or opened. * @throws {DOMException} If the request is synchronous. */ set responseType(type) { // ResponseType can only be set when the state is unsent or opened. if (this.readyState !== XMLHttpRequestReadyStateEnum_js_1.default.opened && this.readyState !== XMLHttpRequestReadyStateEnum_js_1.default.unsent) { throw new this[PropertySymbol.window].DOMException(`Failed to set the 'responseType' property on 'XMLHttpRequest': The object's state must be OPENED or UNSENT.`, DOMExceptionNameEnum_js_1.default.invalidStateError); } // Sync requests can only have empty string or 'text' as response type. if (!this.#async) { throw new this[PropertySymbol.window].DOMException(`Failed to set the 'responseType' property on 'XMLHttpRequest': The response type cannot be changed for synchronous requests made from a document.`, DOMExceptionNameEnum_js_1.default.invalidStateError); } this.#responseType = type; } /** * Get response Type. * * @returns Response type. */ get responseType() { return this.#responseType; } /** * Opens the connection. * * @param method Connection method (eg GET, POST). * @param url URL for the connection. * @param [async=true] Asynchronous connection. * @param [user] Username for basic authentication (optional). * @param [password] Password for basic authentication (optional). */ open(method, url, async = true, user, password) { const window = this[PropertySymbol.window]; if (!async && !!this.responseType && this.responseType !== XMLHttpResponseTypeEnum_js_1.default.text) { throw new window.DOMException(`Failed to execute 'open' on 'XMLHttpRequest': Synchronous requests from a document must not set a response type.`, DOMExceptionNameEnum_js_1.default.invalidAccessError); } const headers = new Headers_js_1.default(); if (user) { const authBuffer = Buffer.from(`${user}:${password || ''}`); headers.set('Authorization', 'Basic ' + authBuffer.toString('base64')); } this.#async = async; this.#aborted = false; this.#response = null; this.#responseBody = null; this.#abortController = new window.AbortController(); this.#request = new window.Request(url, { method, headers, signal: this.#abortController.signal, credentials: this.withCredentials ? 'include' : 'omit' }); this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.opened; } /** * Sets a header for the request. * * @param name Header name. * @param value Header value. * @returns Header added. */ setRequestHeader(name, value) { if (this.readyState !== XMLHttpRequestReadyStateEnum_js_1.default.opened) { throw new this[PropertySymbol.window].DOMException(`Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.`, DOMExceptionNameEnum_js_1.default.invalidStateError); } // TODO: Use FetchRequestHeaderUtility.removeForbiddenHeaders() instead. if (FetchRequestHeaderUtility_js_1.default.isHeaderForbidden(name)) { return false; } this.#request.headers.set(name, value); return true; } /** * Gets a header from the server response. * * @param header header Name of header to get. * @returns string Text of the header or null if it doesn't exist. */ getResponseHeader(header) { return this.#response?.headers.get(header) ?? null; } /** * Gets all the response headers. * * @returns A string with all response headers separated by CR+LF. */ getAllResponseHeaders() { if (!this.#response) { return ''; } const result = []; for (const [name, value] of this.#response?.headers) { const lowerName = name.toLowerCase(); if (lowerName !== 'set-cookie' && lowerName !== 'set-cookie2') { result.push(`${name}: ${value}`); } } return result.join('\r\n'); } /** * Sends the request to the server. * * @param body Optional data to send as request body. */ send(body) { if (this.readyState != XMLHttpRequestReadyStateEnum_js_1.default.opened) { throw new this[PropertySymbol.window].DOMException(`Failed to execute 'send' on 'XMLHttpRequest': Connection must be opened before send() is called.`, DOMExceptionNameEnum_js_1.default.invalidStateError); } // When body is a Document, serialize it to a string. if (typeof body === 'object' && body !== null && body[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.documentNode) { body = body.documentElement.outerHTML; } if (this.#async) { this.#sendAsync(body).catch((error) => { throw error; }); } else { this.#sendSync(body); } } /** * Aborts a request. */ abort() { if (this.#aborted) { return; } this.#aborted = true; this.#abortController.abort(); } /** * Sends the request to the server asynchronously. * * @param body Optional data to send as request body. */ async #sendAsync(body) { const window = this[PropertySymbol.window]; const browserFrame = new WindowBrowserContext_js_1.default(window).getBrowserFrame(); const asyncTaskManager = browserFrame[PropertySymbol.asyncTaskManager]; const taskID = asyncTaskManager.startTask(() => this.abort()); this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.loading; this.dispatchEvent(new Event_js_1.default('readystatechange')); this.dispatchEvent(new Event_js_1.default('loadstart')); if (body) { this.#request = new window.Request(this.#request.url, { method: this.#request.method, headers: this.#request.headers, signal: this.#abortController.signal, credentials: this.#request.credentials, body }); } this.#abortController.signal.addEventListener('abort', () => { this.#aborted = true; this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.unsent; this.dispatchEvent(new Event_js_1.default('abort')); this.dispatchEvent(new Event_js_1.default('loadend')); this.dispatchEvent(new Event_js_1.default('readystatechange')); asyncTaskManager.endTask(taskID); }); const onError = (error) => { if (error instanceof DOMException_js_1.default && error.name === DOMExceptionNameEnum_js_1.default.abortError) { if (this.#aborted) { return; } this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.unsent; this.dispatchEvent(new Event_js_1.default('abort')); } else { this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.done; this.dispatchEvent(new ErrorEvent_js_1.default('error', { error, message: error.message })); } this.dispatchEvent(new Event_js_1.default('loadend')); this.dispatchEvent(new Event_js_1.default('readystatechange')); asyncTaskManager.endTask(taskID); }; const fetch = new Fetch_js_1.default({ browserFrame: browserFrame, window: window, url: this.#request.url, init: this.#request }); try { this.#response = await fetch.send(); } catch (error) { onError(error); return; } this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.headersRecieved; this.dispatchEvent(new Event_js_1.default('readystatechange')); const contentLength = this.#response.headers.get('Content-Length'); const contentLengthNumber = contentLength !== null && !isNaN(Number(contentLength)) ? Number(contentLength) : null; let loaded = 0; let data = Buffer.from([]); if (this.#response.body) { let eventError; try { for await (const chunk of this.#response.body) { data = Buffer.concat([data, typeof chunk === 'string' ? Buffer.from(chunk) : chunk]); loaded += chunk.length; // We need to re-throw the error as we don't want it to be caught by the try/catch. try { this.dispatchEvent(new ProgressEvent_js_1.default('progress', { lengthComputable: contentLengthNumber !== null, loaded, total: contentLengthNumber !== null ? contentLengthNumber : 0 })); } catch (error) { eventError = error; throw error; } } } catch (error) { if (error === eventError) { throw error; } onError(error); return; } } this.#responseBody = XMLHttpRequestResponseDataParser_js_1.default.parse({ window: window, responseType: this.#responseType, data, contentType: this.#response.headers.get('Content-Type') || this.#request.headers.get('Content-Type') }); this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.done; asyncTaskManager.endTask(taskID); this.dispatchEvent(new Event_js_1.default('readystatechange')); this.dispatchEvent(new Event_js_1.default('load')); this.dispatchEvent(new Event_js_1.default('loadend')); } /** * Sends the request to the server synchronously. * * @param body Optional data to send as request body. */ #sendSync(body) { const window = this[PropertySymbol.window]; const browserFrame = new WindowBrowserContext_js_1.default(window).getBrowserFrame(); if (body) { this.#request = new window.Request(this.#request.url, { method: this.#request.method, headers: this.#request.headers, signal: this.#abortController.signal, credentials: this.#request.credentials, body }); } this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.loading; const fetch = new SyncFetch_js_1.default({ browserFrame, window: window, url: this.#request.url, init: this.#request }); try { this.#response = fetch.send(); } catch (error) { this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.done; this.dispatchEvent(new ErrorEvent_js_1.default('error', { error, message: error.message })); this.dispatchEvent(new Event_js_1.default('loadend')); this.dispatchEvent(new Event_js_1.default('readystatechange')); return; } this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.headersRecieved; this.#responseBody = XMLHttpRequestResponseDataParser_js_1.default.parse({ window: window, responseType: this.#responseType, data: this.#response.body, contentType: this.#response.headers.get('Content-Type') || this.#request.headers.get('Content-Type') }); this.#readyState = XMLHttpRequestReadyStateEnum_js_1.default.done; this.dispatchEvent(new Event_js_1.default('readystatechange')); this.dispatchEvent(new Event_js_1.default('load')); this.dispatchEvent(new Event_js_1.default('loadend')); } } exports.default = XMLHttpRequest; //# sourceMappingURL=XMLHttpRequest.cjs.map