123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
import * as http from 'http';
|
|||
|
|
import * as https from 'https';
|
||
|
|
import { URL } from 'url';
|
||
|
|
import {
|
||
|
|
BadGatewayException,
|
||
|
|
Injectable,
|
||
|
|
Logger,
|
||
|
|
RequestTimeoutException,
|
||
|
|
} from '@nestjs/common';
|
||
|
|
|
||
|
|
export type TahesabCallOptions = {
|
||
|
|
baseUrl: string;
|
||
|
|
token: string;
|
||
|
|
dbName: string;
|
||
|
|
/** مسیر OpenAPI مثل DoNewMoshtari یا CheckHealth-SSL */
|
||
|
|
path: string;
|
||
|
|
/** کلید body؛ اگر خالی باشد فقط GET بدون body */
|
||
|
|
bodyKey?: string;
|
||
|
|
args?: unknown[];
|
||
|
|
method?: 'GET' | 'POST';
|
||
|
|
allowInsecureTls?: boolean;
|
||
|
|
timeoutMs?: number;
|
||
|
|
};
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class TahesabHttpClient {
|
||
|
|
private readonly logger = new Logger(TahesabHttpClient.name);
|
||
|
|
|
||
|
|
async call<T = unknown>(opts: TahesabCallOptions): Promise<T> {
|
||
|
|
const base = opts.baseUrl.replace(/\/+$/, '');
|
||
|
|
const path = opts.path.replace(/^\/+/, '');
|
||
|
|
const url = new URL(`${base}/${path}`);
|
||
|
|
const method = opts.method ?? (opts.bodyKey ? 'POST' : 'GET');
|
||
|
|
const timeoutMs = opts.timeoutMs ?? 30_000;
|
||
|
|
|
||
|
|
const headers: Record<string, string> = {
|
||
|
|
Authorization: `Bearer ${opts.token}`,
|
||
|
|
DBname: opts.dbName,
|
||
|
|
DBName: opts.dbName,
|
||
|
|
Accept: 'application/json',
|
||
|
|
Host: url.host,
|
||
|
|
};
|
||
|
|
|
||
|
|
let body: string | undefined;
|
||
|
|
if (method === 'POST' && opts.bodyKey) {
|
||
|
|
headers['Content-Type'] = 'application/json';
|
||
|
|
body = JSON.stringify({ [opts.bodyKey]: opts.args ?? [] });
|
||
|
|
headers['Content-Length'] = Buffer.byteLength(body).toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
const isHttps = url.protocol === 'https:';
|
||
|
|
const lib = isHttps ? https : http;
|
||
|
|
const agent =
|
||
|
|
isHttps && opts.allowInsecureTls
|
||
|
|
? new https.Agent({ rejectUnauthorized: false })
|
||
|
|
: undefined;
|
||
|
|
|
||
|
|
return new Promise<T>((resolve, reject) => {
|
||
|
|
const req = lib.request(
|
||
|
|
{
|
||
|
|
protocol: url.protocol,
|
||
|
|
hostname: url.hostname,
|
||
|
|
port: url.port || (isHttps ? 443 : 80),
|
||
|
|
path: `${url.pathname}${url.search}`,
|
||
|
|
method,
|
||
|
|
headers,
|
||
|
|
timeout: timeoutMs,
|
||
|
|
agent,
|
||
|
|
},
|
||
|
|
(res) => {
|
||
|
|
const chunks: Buffer[] = [];
|
||
|
|
res.on('data', (c) => chunks.push(c));
|
||
|
|
res.on('end', () => {
|
||
|
|
const text = Buffer.concat(chunks).toString('utf8');
|
||
|
|
let json: unknown = null;
|
||
|
|
if (text) {
|
||
|
|
try {
|
||
|
|
json = JSON.parse(text);
|
||
|
|
} catch {
|
||
|
|
json = { raw: text };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const status = res.statusCode ?? 0;
|
||
|
|
if (status < 200 || status >= 300) {
|
||
|
|
this.logger.warn(
|
||
|
|
`Tahesab ${method} ${path} → ${status}: ${text.slice(0, 400)}`,
|
||
|
|
);
|
||
|
|
reject(
|
||
|
|
new BadGatewayException(
|
||
|
|
`پاسخ نامعتبر تهحساب (${status}) برای ${path}`,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
resolve(json as T);
|
||
|
|
});
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
req.on('timeout', () => {
|
||
|
|
req.destroy();
|
||
|
|
reject(
|
||
|
|
new RequestTimeoutException(
|
||
|
|
`مهلت اتصال به تهحساب برای ${path} به پایان رسید.`,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
req.on('error', (err) => {
|
||
|
|
this.logger.error(`Tahesab call failed: ${path}`, err);
|
||
|
|
reject(
|
||
|
|
new BadGatewayException(
|
||
|
|
`اتصال به تهحساب ناموفق بود: ${err.message}`,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
if (body) req.write(body);
|
||
|
|
req.end();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|