Compare commits

...

4 Commits

Author SHA1 Message Date
b4f191de42 fix: basic connection example 2026-04-05 19:57:26 -05:00
9d25b3e6a9 fix: port by default 443 2026-04-03 23:35:10 -05:00
e6a6327b08 Add ts-node as dev dependency 2026-04-03 21:40:19 -05:00
5f0fce2c33 Add HTTP redirect following support
- Add followRedirects option in Transport (enabled by default)
- Add redirectCount to limit redirects to 3
- Detect and follow 301/302/307/308 redirects
- Add forceHttps parameter for redirect handling
- Log redirects when verbose mode is enabled
2026-04-03 21:39:20 -05:00
5 changed files with 974 additions and 752 deletions

View File

@@ -18,10 +18,23 @@ import { TrytonClient } from "../src/client";
// ====================================================
// CONFIGURACIÓN - EDITA ESTOS VALORES CON TUS DATOS
// ====================================================
// const config = {
// hostname: "https://demo7.6.tryton.org", // Servidor demo con HTTPS
// port: 8000, // Puerto del servidor Tryton (generalmente 8000)
// database: "demo7.6", // Base de datos demo
// username: "admin", // Usuario demo
// password: "admin", // Contraseña demo
// language: "es", // Idioma (es, en, etc.)
// options: {
// verbose: true, // Mostrar información detallada de conexión
// timeout: 30000, // Timeout en milisegundos
// },
// };
const config = {
hostname: "https://demo7.6.tryton.org", // Servidor demo con HTTPS
port: 8000, // Puerto del servidor Tryton (generalmente 8000)
database: "demo7.6", // Base de datos demo
hostname: "demo7.6.tryton.org", // Servidor demo con HTTPS
port: 443, // Puerto del servidor Tryton (generalmente 8000)
database: "tryton", // Base de datos demo
username: "admin", // Usuario demo
password: "admin", // Contraseña demo
language: "es", // Idioma (es, en, etc.)
@@ -42,12 +55,10 @@ function validateConfig() {
const missingFields = requiredFields.filter((field) => !config[field]);
if (missingFields.length > 0) {
console.error(
"❌ Error: Faltan los siguientes campos de configuración:"
);
console.error("❌ Error: Faltan los siguientes campos de configuración:");
missingFields.forEach((field) => console.error(` - ${field}`));
console.error(
"\n💡 Edita este archivo y completa la configuración antes de ejecutar."
"\n💡 Edita este archivo y completa la configuración antes de ejecutar.",
);
process.exit(1);
}
@@ -112,7 +123,7 @@ async function main() {
[], // Dominio: todos los terceros (sin filtros)
["id", "name", "code"], // Campos a obtener
0, // Offset
20 // Límite de resultados
20, // Límite de resultados
// Sin parámetro order
);
@@ -125,9 +136,7 @@ async function main() {
partiesData.forEach((party, index) => {
const code = party.code ? ` (${party.code})` : "";
console.log(
`${(index + 1).toString().padStart(2)}. ${
party.name
}${code}`
`${(index + 1).toString().padStart(2)}. ${party.name}${code}`,
);
});
}
@@ -135,8 +144,7 @@ async function main() {
console.log("\n🎉 ¡Ejemplo completado exitosamente!");
} catch (error) {
console.error("❌ Error durante la ejecución:");
const errorMessage =
error instanceof Error ? error.message : String(error);
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(errorMessage);
if (config.options.verbose && error instanceof Error && error.stack) {
@@ -148,9 +156,7 @@ async function main() {
console.error("\n💡 Posibles soluciones:");
console.error(" - Verifica que el servidor Tryton esté ejecutándose");
console.error(" - Confirma que los datos de conexión sean correctos");
console.error(
" - Asegúrate de que el usuario tenga permisos adecuados"
);
console.error(" - Asegúrate de que el usuario tenga permisos adecuados");
console.error(" - Verifica la conectividad de red al servidor");
process.exit(1);

188
package-lock.json generated
View File

@@ -11,12 +11,26 @@
"devDependencies": {
"@types/node": "^20.19.21",
"rimraf": "^5.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.0.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -35,6 +49,34 @@
"node": ">=12"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -46,6 +88,34 @@
"node": ">=14"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz",
@@ -56,6 +126,32 @@
"undici-types": "~6.21.0"
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
@@ -82,6 +178,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -119,6 +222,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -134,6 +244,16 @@
"node": ">= 8"
}
},
"node_modules/diff": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -226,6 +346,13 @@
"dev": true,
"license": "ISC"
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"license": "ISC"
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -442,6 +569,50 @@
"node": ">=8"
}
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -463,6 +634,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -576,6 +754,16 @@
"engines": {
"node": ">=8"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
}
}
}

View File

@@ -37,6 +37,7 @@
"devDependencies": {
"@types/node": "^20.19.21",
"rimraf": "^5.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.0.0"
},
"files": [

View File

@@ -55,7 +55,7 @@ export class TrytonClient {
database,
username,
password,
port = 8000,
port = 443,
language = "en",
options = {},
} = config;
@@ -102,7 +102,7 @@ export class TrytonClient {
username: string,
password: string,
port: number = 8000,
language: string = "en"
language: string = "en",
): TrytonClient {
return new TrytonClient({
hostname,
@@ -121,18 +121,12 @@ export class TrytonClient {
*/
async connect(): Promise<boolean> {
try {
const proxy = new ServerProxy(
this.hostname,
this.port,
this.database,
{
const proxy = new ServerProxy(this.hostname, this.port, this.database, {
verbose: this.options.verbose || false,
connectTimeout:
this.options.connectTimeout || CONNECT_TIMEOUT,
connectTimeout: this.options.connectTimeout || CONNECT_TIMEOUT,
timeout: this.options.timeout || DEFAULT_TIMEOUT,
useHttps: this.useHttps,
}
);
});
// Perform login
const parameters = {
@@ -158,12 +152,11 @@ export class TrytonClient {
session: this.session,
cache: this.options.cache !== false ? [] : null, // Enable cache by default
verbose: this.options.verbose || false,
connectTimeout:
this.options.connectTimeout || CONNECT_TIMEOUT,
connectTimeout: this.options.connectTimeout || CONNECT_TIMEOUT,
timeout: this.options.timeout || DEFAULT_TIMEOUT,
keepMax: this.options.keepMax || 4,
useHttps: this.useHttps,
}
},
);
return true;
@@ -178,11 +171,7 @@ export class TrytonClient {
* @returns {Promise<void>}
*/
async setupConnection(): Promise<void> {
this.connection = new ServerPool(
this.hostname,
this.port,
this.database,
{
this.connection = new ServerPool(this.hostname, this.port, this.database, {
session: undefined, // Session will be set by restoreSession()
cache: this.options.cache !== false ? [] : null,
verbose: this.options.verbose || false,
@@ -190,8 +179,7 @@ export class TrytonClient {
timeout: this.options.timeout || DEFAULT_TIMEOUT,
keepMax: this.options.keepMax || 4,
useHttps: this.useHttps,
}
);
});
}
/**
@@ -247,7 +235,7 @@ export class TrytonClient {
model: ModelName,
ids: RecordIds,
fields: FieldName[],
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<T[]> {
return this.call<T[]>(`model.${model}.read`, [ids, fields, context]);
}
@@ -262,12 +250,9 @@ export class TrytonClient {
async create(
model: ModelName,
records: Record<string, any>[],
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<RecordIds> {
return this.call<RecordIds>(`model.${model}.create`, [
records,
context,
]);
return this.call<RecordIds>(`model.${model}.create`, [records, context]);
}
/**
@@ -282,7 +267,7 @@ export class TrytonClient {
model: ModelName,
ids: RecordIds,
values: Record<string, any>,
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<void> {
return this.call<void>(`model.${model}.write`, [ids, values, context]);
}
@@ -297,7 +282,7 @@ export class TrytonClient {
async delete(
model: ModelName,
ids: RecordIds,
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<void> {
return this.call<void>(`model.${model}.delete`, [ids, context]);
}
@@ -318,7 +303,7 @@ export class TrytonClient {
offset: number = 0,
limit: number | null = null,
order: string[] | null = null,
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<RecordIds> {
return this.call<RecordIds>(`model.${model}.search`, [
domain,
@@ -347,7 +332,7 @@ export class TrytonClient {
offset: number = 0,
limit: number | null = null,
order: string[] | null = null,
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<T[]> {
return this.call<T[]>(`model.${model}.search_read`, [
domain,
@@ -369,12 +354,9 @@ export class TrytonClient {
async searchCount(
model: ModelName,
domain: SearchDomain,
context: TrytonContext = {}
context: TrytonContext = {},
): Promise<number> {
return this.call<number>(`model.${model}.search_count`, [
domain,
context,
]);
return this.call<number>(`model.${model}.search_count`, [domain, context]);
}
/**
@@ -419,7 +401,7 @@ export class TrytonClient {
const users = await this.read<TrytonUser>(
"res.user",
[preferences.id],
["id", "name", "login", "language", "company", "email"]
["id", "name", "login", "language", "company", "email"],
);
if (!users || users.length === 0) {
throw new Error("User not found");
@@ -570,10 +552,7 @@ export class TrytonClient {
// Test de validación de sesión
try {
// Usar un método que requiere autenticación para validar la sesión
await proxy.request("model.res.user.get_preferences", [
true,
{},
]);
await proxy.request("model.res.user.get_preferences", [true, {}]);
this.connection.putConnection(proxy); // Devolver conexión al pool
return true;
} catch (error) {
@@ -595,36 +574,20 @@ export class TrytonClient {
* Creates a typed interface for specific models
*/
model<T extends TrytonRecord = TrytonRecord>(
modelName: ModelName
modelName: ModelName,
): TypedModelOperations<T> {
return {
read: (
ids: RecordIds,
fields: FieldName[],
context?: TrytonContext
) => this.read<T>(modelName, ids, fields, context),
read: (ids: RecordIds, fields: FieldName[], context?: TrytonContext) =>
this.read<T>(modelName, ids, fields, context),
create: (
records: Partial<Omit<T, "id">>[],
context?: TrytonContext
) =>
this.create(
modelName,
records as Record<string, any>[],
context
),
create: (records: Partial<Omit<T, "id">>[], context?: TrytonContext) =>
this.create(modelName, records as Record<string, any>[], context),
write: (
ids: RecordIds,
values: Partial<Omit<T, "id">>,
context?: TrytonContext
) =>
this.write(
modelName,
ids,
values as Record<string, any>,
context
),
context?: TrytonContext,
) => this.write(modelName, ids, values as Record<string, any>, context),
delete: (ids: RecordIds, context?: TrytonContext) =>
this.delete(modelName, ids, context),
@@ -634,7 +597,7 @@ export class TrytonClient {
offset?: number,
limit?: number,
order?: string[],
context?: TrytonContext
context?: TrytonContext,
) => this.search(modelName, domain, offset, limit, order, context),
searchRead: (
@@ -643,7 +606,7 @@ export class TrytonClient {
offset?: number,
limit?: number,
order?: string[],
context?: TrytonContext
context?: TrytonContext,
) =>
this.searchRead<T>(
modelName,
@@ -652,7 +615,7 @@ export class TrytonClient {
offset,
limit,
order,
context
context,
),
searchCount: (domain: SearchDomain, context?: TrytonContext) =>

View File

@@ -241,6 +241,7 @@ interface TransportOptions {
connectTimeout?: number | undefined;
timeout?: number | undefined;
useHttps?: boolean;
followRedirects?: boolean;
}
interface JsonRpcRequest {
@@ -267,6 +268,8 @@ export class Transport {
private connectTimeout: number;
private timeout: number;
private useHttps: boolean;
private followRedirects: boolean;
private redirectCount: number;
constructor(options: TransportOptions = {}) {
this.fingerprints = options.fingerprints || null;
@@ -276,6 +279,8 @@ export class Transport {
this.connectTimeout = options.connectTimeout || CONNECT_TIMEOUT;
this.timeout = options.timeout || DEFAULT_TIMEOUT;
this.useHttps = options.useHttps || false;
this.followRedirects = options.followRedirects ?? true;
this.redirectCount = 0;
}
/**
@@ -291,6 +296,21 @@ export class Transport {
handler: string,
requestData: string,
verbose: boolean = false
): Promise<JsonRpcResponse> {
// Reset redirect count for each new request
this.redirectCount = 0;
return this.doRequest(host, handler, requestData, verbose);
}
/**
* Internal method to make HTTP request with redirect support
*/
private async doRequest(
host: string,
handler: string,
requestData: string,
verbose: boolean = false,
forceHttps?: boolean
): Promise<JsonRpcResponse> {
// Detect protocol based on port or explicit protocol
const hostParts = host.split(":");
@@ -299,6 +319,7 @@ export class Transport {
// Use HTTPS if explicitly configured, or for standard HTTPS ports
const shouldUseHttps =
forceHttps === true ||
this.useHttps ||
port === 443 ||
port === 8443 ||
@@ -364,6 +385,49 @@ export class Transport {
res.on("end", () => {
try {
// Handle redirects BEFORE parsing response body
const statusCode = res.statusCode;
if (
this.followRedirects &&
statusCode &&
this.redirectCount < 3 &&
[301, 302, 307, 308].includes(statusCode)
) {
const location = res.headers["location"];
if (location) {
this.redirectCount++;
if (verbose) {
console.log(
`🔄 Redirect (${statusCode}): ${host}${handler}${location}`
);
}
try {
const redirectUrl = new URL(
location,
`http://${host}`
);
const newHost = redirectUrl.host;
const newUseHttps =
redirectUrl.protocol === "https:";
this.doRequest(
newHost,
handler,
requestData,
verbose,
newUseHttps
)
.then(resolve)
.catch(reject);
return;
} catch (redirectError) {
reject(redirectError);
return;
}
}
}
// Handle compression
const encoding = res.headers["content-encoding"];
let responseText: string;