Sign In

@applitools/req

Package Overview
Dependencies
Maintainers
49
Versions
101
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@applitools/req - npm Package Compare versions

Comparing version
1.8.8
to
1.9.0
+158
dist/fetch-http2.js
import { fetch as undiciFetch, Agent } from 'undici';
import { Response as NodeFetchResponse } from 'node-fetch';
import { lookupWithCache } from './dns-cache.js';
import { Readable } from 'stream';
import { Buffer } from 'buffer';
import * as utils from '@applitools/utils';
const cachify = utils.general.cachify;
const cachifiedCreateAgent = cachify((options) => {
return new Agent({
allowH2: options.allowH2,
maxHeaderSize: 16384,
keepAliveTimeout: options.keepAliveTimeout,
keepAliveMaxTimeout: options.keepAliveMaxTimeout,
connect: { timeout: 60000, lookup: options.useDnsCache ? lookupWithCache : undefined },
bodyTimeout: 300000,
headersTimeout: 300000,
});
});
const makeDispatcher = cachify(() => {
return function dispatcher(url) {
const isHttps = url.protocol === 'https:';
// HTTP/2 requires TLS — only allow H2 for HTTPS URLs
const allowH2 = isHttps;
const useDnsCache = utils.general.getEnvValue('USE_DNS_CACHE') === 'true';
const agentOptions = {
allowH2,
keepAliveTimeout: 30000,
keepAliveMaxTimeout: 300000,
useDnsCache,
};
return cachifiedCreateAgent(agentOptions);
};
});
// --- OPTIMIZATION 1: Get Body Length without reading the body ---
function getBodyLength(url, body, headers) {
if (url.size) {
return String(url.size);
}
if (headers && (headers['content-length'] || headers['Content-Length'])) {
return headers['content-length'] || headers['Content-Length'];
}
if (Buffer.isBuffer(body))
return String(body.length);
if (typeof body === 'string')
return String(Buffer.byteLength(body));
if (body && body.size)
return String(body.size);
return undefined;
}
export const fetchImpl = async (url, options = {}) => {
var _a, _b, _c, _d, _e;
let finalUrl;
const finalOptions = { ...options };
// 1. Parameter Normalization
if (typeof url === 'object' && 'url' in url && !(url instanceof URL)) {
const req = url; // Treated as NodeFetchRequest
finalUrl = req.url;
(_a = finalOptions.method) !== null && _a !== void 0 ? _a : (finalOptions.method = req.method);
(_b = finalOptions.headers) !== null && _b !== void 0 ? _b : (finalOptions.headers = {});
// Copy headers from the request object
if (typeof ((_c = req.headers) === null || _c === void 0 ? void 0 : _c.forEach) === 'function') {
req.headers.forEach((v, k) => {
finalOptions.headers[k.toLowerCase()] = v;
});
}
else {
Object.assign(finalOptions.headers, req.headers);
}
// --- OPTIMIZATION 2: Don't Buffer. Use the stream directly. ---
// If we haven't provided a body, use the one from the request.
if (!finalOptions.body) {
finalOptions.body = req.body;
}
(_d = finalOptions.timeout) !== null && _d !== void 0 ? _d : (finalOptions.timeout = req.timeout);
}
else {
finalUrl = url;
}
// 2. Dispatcher Setup
if (!finalOptions.dispatcher) {
const urlObj = typeof finalUrl === 'string' ? new URL(finalUrl) : finalUrl;
finalOptions.dispatcher = (_e = makeDispatcher()) === null || _e === void 0 ? void 0 : _e(urlObj);
}
// 3. Stream Handling (Crucial for Images)
const isStream = finalOptions.body &&
(typeof finalOptions.body.pipe === 'function' || typeof finalOptions.body[Symbol.asyncIterator] === 'function');
if (isStream) {
// Undici MUST have 'duplex: half' to stream a request body
finalOptions.duplex = 'half';
}
const detectedSize = getBodyLength(url, finalOptions.body, finalOptions.headers);
if (detectedSize) {
// set content-length header only if it's not already set by the user, and if we were able to detect the size
const contentLengthHeader = Object.keys(finalOptions.headers).find((h) => h.toLowerCase() === 'content-length');
if (!contentLengthHeader) {
finalOptions.headers = { ...finalOptions.headers, 'content-length': detectedSize };
}
}
let timeoutId;
let internalController;
if (finalOptions.timeout) {
internalController = new AbortController();
timeoutId = setTimeout(() => internalController === null || internalController === void 0 ? void 0 : internalController.abort(), finalOptions.timeout);
if (finalOptions.signal) {
finalOptions.signal.addEventListener('abort', () => internalController === null || internalController === void 0 ? void 0 : internalController.abort(), { once: true });
}
finalOptions.signal = internalController.signal;
}
// Cleanup
delete finalOptions.timeout;
delete finalOptions.agent;
delete finalOptions.size;
try {
const response = await undiciFetch(finalUrl, finalOptions);
// Return stream to caller (Don't buffer response either!)
const nodeBody = response.body ? Readable.from(response.body) : undefined;
return new NodeFetchResponse(nodeBody, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
finally {
if (timeoutId)
clearTimeout(timeoutId);
}
};
// utility function to make transition from node-fetch to undici easier
// and to avoid converting streams to buffers just to get their length in fetch-http2 implementation
export function getBodySize(input, opts) {
var _a, _b;
// 1. Determine Method EARLY so we can decide if body is allowed
const method = (_a = opts.method) !== null && _a !== void 0 ? _a : input.method;
const isBodyAllowed = method && method.toUpperCase() !== 'GET' && method.toUpperCase() !== 'HEAD';
let finalSize = undefined;
let finalBody = (_b = opts.body) !== null && _b !== void 0 ? _b : input.body;
if (isBodyAllowed) {
if (opts.size) {
finalSize = opts.size;
}
else {
if (Buffer.isBuffer(finalBody)) {
finalSize = finalBody.length;
}
else if (finalBody instanceof Uint8Array) {
finalSize = finalBody.byteLength;
}
else if (typeof finalBody === 'string') {
finalSize = Buffer.byteLength(finalBody);
}
else if (utils.types.isPlainObject(finalBody) || utils.types.isArray(finalBody) || finalBody === null) {
finalBody = JSON.stringify(finalBody);
finalSize = Buffer.byteLength(finalBody);
}
}
}
return finalSize;
}
+7
-0
# Changelog
## [1.9.0](https://github.com/Applitools-Dev/sdk/compare/js/req@1.8.8...js/req@1.9.0) (2026-03-02)
### Features
* http 2 support | FLD-4085 ([#3539](https://github.com/Applitools-Dev/sdk/issues/3539)) ([b304d4a](https://github.com/Applitools-Dev/sdk/commit/b304d4a1072a201d1c3f5cea1275b6a15230d6aa))
## [1.8.8](https://github.com/Applitools-Dev/sdk/compare/js/req@1.8.7...js/req@1.8.8) (2026-02-16)

@@ -4,0 +11,0 @@

+1
-0

@@ -5,1 +5,2 @@ export default globalThis.fetch;

export const Response = globalThis.Response;
export const getBodySize = () => undefined;

@@ -1,2 +0,30 @@

export { default } from 'node-fetch';
export * from 'node-fetch';
import { general } from '@applitools/utils';
const cachify = general.cachify;
const getFetchImpl = cachify(async (httpVersion) => {
let fetchImpl;
let getBodySize = () => undefined;
if (httpVersion === '2') {
const module = await import('./fetch-http2.js');
fetchImpl = module.fetchImpl;
getBodySize = module.getBodySize;
}
else {
const module = await import('node-fetch');
fetchImpl = module.default;
getBodySize = () => undefined;
}
return { fetchImpl, getBodySize };
});
// Export a wrapper that selects implementation based on environment variable
export default async function fetch(...args) {
const httpVersion = general.getEnvValue('HTTP_VERSION') || '';
const { fetchImpl } = await getFetchImpl(httpVersion);
return fetchImpl(...args);
}
export const getBodySize = async (...args) => {
const httpVersion = general.getEnvValue('HTTP_VERSION') || '';
const { getBodySize } = await getFetchImpl(httpVersion);
return getBodySize(...args);
};
// Export runtime classes and types from node-fetch (both implementations use these same types)
export { Request, Headers, Response } from 'node-fetch';
+8
-4

@@ -1,6 +0,5 @@

import { AbortController } from 'abort-controller';
import { stop } from './stop.js';
import { makeAgent } from './agent.js';
import { AbortCode, RequestTimeoutError, ConnectionTimeoutError, RetryTimeoutError } from './req-errors.js';
import globalFetch, { Request, Headers, Response } from './fetch.js';
import globalFetch, { Request, Headers, Response, getBodySize } from './fetch.js';
import * as utils from '@applitools/utils';

@@ -84,3 +83,3 @@ import { Buffer } from 'buffer';

}
let request = new Request(url, {
const requestInit = {
method: (_b = opts.method) !== null && _b !== void 0 ? _b : input.method,

@@ -100,3 +99,8 @@ headers: {

signal: requestController.signal,
});
};
const finalSize = await getBodySize(input, opts);
if (finalSize) {
requestInit.size = finalSize;
}
let request = new Request(url, requestInit);
request = await beforeRequest({ request, options: opts });

@@ -103,0 +107,0 @@ return request;

{
"name": "@applitools/req",
"version": "1.8.8",
"version": "1.9.0",
"description": "Applitools fetch-based request library",

@@ -46,2 +46,3 @@ "keywords": [

"./dist/agent.js": "./dist/agent-browser.js",
"./dist/fetch-http2.js": "./dist/fetch-browser.js",
"./dist/fetch.js": "./dist/fetch-browser.js"

@@ -58,7 +59,8 @@ },

"build:cjs": "esbuild ./dist/index.js --outfile=./dist/index.cjs --bundle --platform=node --format=cjs --target=node12 && cp ./types/index.d.ts ./types/index.d.cts",
"test": "run --top-level mocha './test/**/*.spec.ts'"
"test": "MOCHA_OMIT_TAGS=http2 run --top-level mocha './test/**/*.spec.ts'",
"test:http2": "APPLITOOLS_HTTP_VERSION=2 MOCHA_OMIT_TAGS=http1 run --top-level mocha './test/**/*.spec.ts'"
},
"dependencies": {
"@applitools/logger": "2.2.8",
"@applitools/utils": "1.14.1",
"abort-controller": "3.0.0",
"http-proxy-agent": "5.0.0",

@@ -80,2 +82,5 @@ "https-proxy-agent": "5.0.1",

},
"optionalDependencies": {
"undici": ">=6.23.0 <8.0.0"
},
"publishConfig": {

@@ -82,0 +87,0 @@ "access": "public"

@@ -23,21 +23,2 @@ export type Stop = stop;

}
export interface RequestInit {
body?: undefined | null | BodyInit;
headers?: undefined | HeadersInit;
method?: undefined | string;
redirect?: undefined | ("error" | "follow" | "manual");
signal?: undefined | null | { readonly aborted: boolean; addEventListener: (type: "abort", listener: () => void) => void; removeEventListener: (type: "abort", listener: () => void) => void; };
referrer?: undefined | string;
referrerPolicy?: undefined | ("" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url");
agent?: undefined | boolean | import('http').Agent | ((parsedUrl: URL) => undefined | boolean | import('http').Agent);
compress?: undefined | boolean;
counter?: undefined | number;
follow?: undefined | number;
hostname?: undefined | string;
port?: undefined | number;
protocol?: undefined | string;
size?: undefined | number;
highWaterMark?: undefined | number;
insecureHTTPParser?: undefined | boolean;
}
export class Response {

@@ -66,7 +47,2 @@ static error(): Response;

}
export interface ResponseInit {
headers?: undefined | HeadersInit;
status?: undefined | number;
statusText?: undefined | string;
}
export class Headers {

@@ -86,2 +62,26 @@ constructor(init?: undefined | HeadersInit);

}
export interface RequestInit {
body?: undefined | null | BodyInit;
headers?: undefined | HeadersInit;
method?: undefined | string;
redirect?: undefined | ("error" | "follow" | "manual");
signal?: undefined | null | { readonly aborted: boolean; addEventListener: (type: "abort", listener: () => void) => void; removeEventListener: (type: "abort", listener: () => void) => void; };
referrer?: undefined | string;
referrerPolicy?: undefined | ("" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url");
agent?: undefined | boolean | import('http').Agent | ((parsedUrl: URL) => undefined | boolean | import('http').Agent);
compress?: undefined | boolean;
counter?: undefined | number;
follow?: undefined | number;
hostname?: undefined | string;
port?: undefined | number;
protocol?: undefined | string;
size?: undefined | number;
highWaterMark?: undefined | number;
insecureHTTPParser?: undefined | boolean;
}
export interface ResponseInit {
headers?: undefined | HeadersInit;
status?: undefined | number;
statusText?: undefined | string;
}
export type HeadersInit = Record<string, string> | Headers | Iterable<[string, string]> | Iterable<Iterable<string>>;

@@ -102,3 +102,3 @@ export type BodyInit = string | NodeJS.ReadableStream | Blob | Buffer | URLSearchParams | FormData;

}
export type Fetch = (url: URL | (string | Request), init?: undefined | RequestInit) => Promise<Response>;
export type Fetch = (...args: Array<any>) => Promise<any>;
export interface Options {

@@ -121,2 +121,3 @@ baseUrl?: undefined | string;

keepAliveOptions?: undefined | KeepAliveOptions;
size?: undefined | number;
}

@@ -123,0 +124,0 @@ export type Retry = {

@@ -23,21 +23,2 @@ export type Stop = stop;

}
export interface RequestInit {
body?: undefined | null | BodyInit;
headers?: undefined | HeadersInit;
method?: undefined | string;
redirect?: undefined | ("error" | "follow" | "manual");
signal?: undefined | null | { readonly aborted: boolean; addEventListener: (type: "abort", listener: () => void) => void; removeEventListener: (type: "abort", listener: () => void) => void; };
referrer?: undefined | string;
referrerPolicy?: undefined | ("" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url");
agent?: undefined | boolean | import('http').Agent | ((parsedUrl: URL) => undefined | boolean | import('http').Agent);
compress?: undefined | boolean;
counter?: undefined | number;
follow?: undefined | number;
hostname?: undefined | string;
port?: undefined | number;
protocol?: undefined | string;
size?: undefined | number;
highWaterMark?: undefined | number;
insecureHTTPParser?: undefined | boolean;
}
export class Response {

@@ -66,7 +47,2 @@ static error(): Response;

}
export interface ResponseInit {
headers?: undefined | HeadersInit;
status?: undefined | number;
statusText?: undefined | string;
}
export class Headers {

@@ -86,2 +62,26 @@ constructor(init?: undefined | HeadersInit);

}
export interface RequestInit {
body?: undefined | null | BodyInit;
headers?: undefined | HeadersInit;
method?: undefined | string;
redirect?: undefined | ("error" | "follow" | "manual");
signal?: undefined | null | { readonly aborted: boolean; addEventListener: (type: "abort", listener: () => void) => void; removeEventListener: (type: "abort", listener: () => void) => void; };
referrer?: undefined | string;
referrerPolicy?: undefined | ("" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url");
agent?: undefined | boolean | import('http').Agent | ((parsedUrl: URL) => undefined | boolean | import('http').Agent);
compress?: undefined | boolean;
counter?: undefined | number;
follow?: undefined | number;
hostname?: undefined | string;
port?: undefined | number;
protocol?: undefined | string;
size?: undefined | number;
highWaterMark?: undefined | number;
insecureHTTPParser?: undefined | boolean;
}
export interface ResponseInit {
headers?: undefined | HeadersInit;
status?: undefined | number;
statusText?: undefined | string;
}
export type HeadersInit = Record<string, string> | Headers | Iterable<[string, string]> | Iterable<Iterable<string>>;

@@ -102,3 +102,3 @@ export type BodyInit = string | NodeJS.ReadableStream | Blob | Buffer | URLSearchParams | FormData;

}
export type Fetch = (url: URL | (string | Request), init?: undefined | RequestInit) => Promise<Response>;
export type Fetch = (...args: Array<any>) => Promise<any>;
export interface Options {

@@ -121,2 +121,3 @@ baseUrl?: undefined | string;

keepAliveOptions?: undefined | KeepAliveOptions;
size?: undefined | number;
}

@@ -123,0 +124,0 @@ export type Retry = {

Sorry, the diff of this file is too big to display