happy-dom
Version:
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.
372 lines • 13.6 kB
JavaScript
"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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__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 PropertySymbol = __importStar(require("../PropertySymbol.cjs"));
const Blob_js_1 = __importDefault(require("../file/Blob.cjs"));
const Headers_js_1 = __importDefault(require("./Headers.cjs"));
const url_1 = require("url");
const URL_js_1 = __importDefault(require("../url/URL.cjs"));
const FetchBodyUtility_js_1 = __importDefault(require("./utilities/FetchBodyUtility.cjs"));
const DOMExceptionNameEnum_js_1 = __importDefault(require("../exception/DOMExceptionNameEnum.cjs"));
const MultipartFormDataParser_js_1 = __importDefault(require("./multipart/MultipartFormDataParser.cjs"));
const buffer_1 = require("buffer");
const WindowBrowserContext_js_1 = __importDefault(require("../window/WindowBrowserContext.cjs"));
const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
/**
* Fetch response.
*
* Based on:
* https://github.com/node-fetch/node-fetch/blob/main/src/response.js (MIT)
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response/Response
*/
class Response {
// Public properties
body = null;
bodyUsed = false;
redirected = false;
type = 'basic';
url = '';
status;
statusText;
ok;
headers;
[PropertySymbol.cachedResponse] = null;
[PropertySymbol.buffer] = null;
/**
* Constructor.
*
* @param body Body.
* @param [init] Init.
*/
constructor(body, init) {
if (!this[PropertySymbol.window]) {
throw new TypeError(`Failed to construct '${this.constructor.name}': '${this.constructor.name}' was constructed outside a Window context.`);
}
this.status = init?.status !== undefined ? init.status : 200;
this.statusText = init?.statusText || '';
this.ok = this.status >= 200 && this.status < 300;
this.headers = new Headers_js_1.default(init?.headers);
// "Set-Cookie" and "Set-Cookie2" are not allowed in response headers according to spec.
this.headers.delete('Set-Cookie');
this.headers.delete('Set-Cookie2');
if (body) {
const { stream, buffer, contentType } = FetchBodyUtility_js_1.default.getBodyStream(body);
this.body = stream;
if (buffer) {
this[PropertySymbol.buffer] = buffer;
}
if (contentType && !this.headers.has('Content-Type')) {
this.headers.set('Content-Type', contentType);
}
}
}
/**
* Returns string tag.
*
* @returns String tag.
*/
get [Symbol.toStringTag]() {
return 'Response';
}
/**
* Returns array buffer.
*
* @returns Array buffer.
*/
async arrayBuffer() {
const window = this[PropertySymbol.window];
if (this.bodyUsed) {
throw new window.DOMException(`Body has already been used for "${this.url}".`, DOMExceptionNameEnum_js_1.default.invalidStateError);
}
const browserFrame = new WindowBrowserContext_js_1.default(window).getBrowserFrame();
// No browser frame means that the browser is being teared down.
if (!browserFrame) {
return new ArrayBuffer(0);
}
const asyncTaskManager = browserFrame[PropertySymbol.asyncTaskManager];
this.bodyUsed = true;
let buffer = this[PropertySymbol.buffer];
if (!buffer) {
const taskID = asyncTaskManager.startTask(() => {
if (this.body) {
this.body[PropertySymbol.aborted] = true;
}
});
try {
buffer = await FetchBodyUtility_js_1.default.consumeBodyStream(window, this.body);
}
catch (error) {
asyncTaskManager.endTask(taskID);
throw error;
}
asyncTaskManager.endTask(taskID);
}
this.#storeBodyInCache(buffer);
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
}
/**
* Returns blob.
*
* @returns Blob.
*/
async blob() {
const type = this.headers.get('Content-Type') || '';
const buffer = await this.arrayBuffer();
return new Blob_js_1.default([buffer], { type });
}
/**
* Returns buffer.
*
* @returns Buffer.
*/
async buffer() {
const window = this[PropertySymbol.window];
if (this.bodyUsed) {
throw new window.DOMException(`Body has already been used for "${this.url}".`, DOMExceptionNameEnum_js_1.default.invalidStateError);
}
const browserFrame = new WindowBrowserContext_js_1.default(window).getBrowserFrame();
// No browser frame means that the browser is being teared down.
if (!browserFrame) {
return buffer_1.Buffer.alloc(0);
}
const asyncTaskManager = browserFrame[PropertySymbol.asyncTaskManager];
this.bodyUsed = true;
let buffer = this[PropertySymbol.buffer];
if (!buffer) {
const taskID = asyncTaskManager.startTask(() => {
if (this.body) {
this.body[PropertySymbol.aborted] = true;
}
});
try {
buffer = await FetchBodyUtility_js_1.default.consumeBodyStream(window, this.body);
}
catch (error) {
asyncTaskManager.endTask(taskID);
throw error;
}
asyncTaskManager.endTask(taskID);
}
this.#storeBodyInCache(buffer);
return buffer;
}
/**
* Returns text.
*
* @returns Text.
*/
async text() {
const window = this[PropertySymbol.window];
if (this.bodyUsed) {
throw new window.DOMException(`Body has already been used for "${this.url}".`, DOMExceptionNameEnum_js_1.default.invalidStateError);
}
const browserFrame = new WindowBrowserContext_js_1.default(window).getBrowserFrame();
// No browser frame means that the browser is being teared down.
if (!browserFrame) {
return '';
}
const asyncTaskManager = browserFrame[PropertySymbol.asyncTaskManager];
this.bodyUsed = true;
let buffer = this[PropertySymbol.buffer];
if (!buffer) {
const taskID = asyncTaskManager.startTask(() => {
if (this.body) {
this.body[PropertySymbol.aborted] = true;
}
});
try {
buffer = await FetchBodyUtility_js_1.default.consumeBodyStream(window, this.body);
}
catch (error) {
asyncTaskManager.endTask(taskID);
throw error;
}
asyncTaskManager.endTask(taskID);
}
this.#storeBodyInCache(buffer);
return new TextDecoder().decode(buffer);
}
/**
* Returns json.
*
* @returns JSON.
*/
async json() {
const text = await this.text();
return JSON.parse(text);
}
/**
* Returns form data.
*
* @returns Form data.
*/
async formData() {
const window = this[PropertySymbol.window];
const browserFrame = new WindowBrowserContext_js_1.default(window).getBrowserFrame();
// No browser frame means that the browser is being teared down.
if (!browserFrame) {
return new window.FormData();
}
const asyncTaskManager = browserFrame[PropertySymbol.asyncTaskManager];
const contentType = this.headers.get('Content-Type');
if (/multipart/i.test(contentType)) {
if (this.bodyUsed) {
throw new window.DOMException(`Body has already been used for "${this.url}".`, DOMExceptionNameEnum_js_1.default.invalidStateError);
}
this.bodyUsed = true;
const taskID = browserFrame[PropertySymbol.asyncTaskManager].startTask(() => {
if (this.body) {
this.body[PropertySymbol.aborted] = true;
}
});
let formData;
let buffer;
try {
const result = await MultipartFormDataParser_js_1.default.streamToFormData(window, this.body, contentType);
formData = result.formData;
buffer = result.buffer;
}
catch (error) {
asyncTaskManager.endTask(taskID);
throw error;
}
this.#storeBodyInCache(buffer);
asyncTaskManager.endTask(taskID);
return formData;
}
if (contentType?.startsWith('application/x-www-form-urlencoded')) {
const parameters = new url_1.URLSearchParams(await this.text());
const formData = new window.FormData();
for (const [key, value] of parameters) {
formData.append(key, value);
}
return formData;
}
throw new window.DOMException(`Failed to build FormData object: The "content-type" header is neither "application/x-www-form-urlencoded" nor "multipart/form-data".`, DOMExceptionNameEnum_js_1.default.invalidStateError);
}
/**
* Clones request.
*
* @returns Clone.
*/
clone() {
const window = this[PropertySymbol.window];
const body = FetchBodyUtility_js_1.default.cloneBodyStream(window, this);
const response = new window.Response(body, {
status: this.status,
statusText: this.statusText,
headers: this.headers
});
response[PropertySymbol.cachedResponse] = this[PropertySymbol.cachedResponse];
response[PropertySymbol.buffer] = this[PropertySymbol.buffer];
response.ok = this.ok;
response.redirected = this.redirected;
response.type = this.type;
response.url = this.url;
return response;
}
/**
* Stores body in cache.
*
* @param buffer Buffer.
*/
#storeBodyInCache(buffer) {
if (this[PropertySymbol.cachedResponse]?.response?.waitingForBody) {
this[PropertySymbol.cachedResponse].response.body = buffer;
this[PropertySymbol.cachedResponse].response.waitingForBody = false;
}
}
/**
* Returns a redirect response.
*
* @param url URL.
* @param status Status code.
* @returns Response.
*/
static redirect(url, status = 302) {
const window = this[PropertySymbol.window];
if (!REDIRECT_STATUS_CODES.includes(status)) {
throw new window.DOMException('Failed to create redirect response: Invalid redirect status code.', DOMExceptionNameEnum_js_1.default.invalidStateError);
}
return new window.Response(null, {
headers: {
location: new URL_js_1.default(url).toString()
},
status
});
}
/**
* Returns an error response.
*
* @param url URL.
* @param status Status code.
* @returns Response.
*/
static error() {
const response = new this[PropertySymbol.window].Response(null, { status: 0, statusText: '' });
response.type = 'error';
return response;
}
/**
* Returns an JSON response.
*
* @param injected Injected properties.
* @param data Data.
* @param [init] Init.
* @returns Response.
*/
static json(data, init) {
const window = this[PropertySymbol.window];
const body = JSON.stringify(data);
if (body === undefined) {
throw new window.TypeError('data is not JSON serializable');
}
const headers = new window.Headers(init && init.headers);
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
return new window.Response(body, {
status: 200,
...init,
headers
});
}
}
exports.default = Response;
//# sourceMappingURL=Response.cjs.map