Skip to content

Commit 9179fca

Browse files
authored
Merge pull request #72 from teacoder-team/dev
feat: switch Yookassa client to undici with proxy support
2 parents f1da104 + dca40c5 commit 9179fca

5 files changed

Lines changed: 134 additions & 58 deletions

File tree

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import { HttpService } from '@nestjs/axios';
2-
import type { AxiosRequestConfig } from 'axios';
32
import { type YookassaModuleOptions } from '../../common/interfaces';
43
export declare class YookassaHttpClient {
54
private readonly config;
65
private readonly httpService;
6+
private readonly dispatcher;
77
constructor(config: YookassaModuleOptions, httpService: HttpService);
8-
request<T = any>(options: AxiosRequestConfig): Promise<T>;
8+
request<T = any>(options: {
9+
method: string;
10+
url: string;
11+
data?: any;
12+
params?: any;
13+
}): Promise<T>;
914
get<T>(url: string, params?: any): Promise<T>;
1015
post<T>(url: string, data?: any): Promise<T>;
16+
private buildAuthHeader;
17+
private buildUrl;
18+
private extractProxyFromAgent;
1119
}

packages/nestjs-yookassa/dist/core/http/yookassa.http-client.js

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,45 +14,48 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
1414
Object.defineProperty(exports, "__esModule", { value: true });
1515
exports.YookassaHttpClient = void 0;
1616
const axios_1 = require("@nestjs/axios");
17-
const rxjs_1 = require("rxjs");
1817
const yookassa_error_1 = require("./yookassa.error");
1918
const yookassa_constants_1 = require("../config/yookassa.constants");
2019
const crypto_1 = require("crypto");
2120
const common_1 = require("@nestjs/common");
2221
const interfaces_1 = require("../../common/interfaces");
22+
const undici_1 = require("undici");
2323
let YookassaHttpClient = class YookassaHttpClient {
2424
constructor(config, httpService) {
2525
this.config = config;
2626
this.httpService = httpService;
27-
const client = this.httpService.axiosRef;
28-
client.defaults.baseURL = yookassa_constants_1.YOOKASSA_API_URL;
29-
client.defaults.timeout = 15000;
30-
client.defaults.auth = {
31-
username: this.config.shopId,
32-
password: this.config.apiKey
33-
};
34-
client.defaults.headers.common['Content-Type'] = 'application/json';
35-
client.defaults.proxy = false;
3627
if (this.config.agent) {
37-
client.defaults.httpAgent = this.config.agent;
38-
client.defaults.httpsAgent = this.config.agent;
39-
console.log(`[YooKassa] Proxy agent enabled`);
28+
const proxyUrl = this.extractProxyFromAgent();
29+
this.dispatcher = new undici_1.ProxyAgent(proxyUrl);
30+
console.log('[YooKassa] ProxyAgent enabled:', proxyUrl);
31+
}
32+
else {
33+
this.dispatcher = undefined;
4034
}
4135
}
4236
async request(options) {
43-
var _a, _b, _c, _d, _e;
37+
const url = this.buildUrl(options.url, options.params);
4438
try {
45-
options.headers = Object.assign(Object.assign({}, options.headers), { 'Idempotence-Key': (0, crypto_1.randomUUID)() });
46-
if (this.config.agent) {
47-
options.httpAgent = this.config.agent;
48-
options.httpsAgent = this.config.agent;
49-
options.proxy = false;
39+
const res = await (0, undici_1.request)(url, {
40+
method: options.method,
41+
dispatcher: this.dispatcher,
42+
headersTimeout: 15000,
43+
bodyTimeout: 15000,
44+
headers: {
45+
'Content-Type': 'application/json',
46+
'Idempotence-Key': (0, crypto_1.randomUUID)(),
47+
Authorization: this.buildAuthHeader()
48+
},
49+
body: options.data ? JSON.stringify(options.data) : undefined
50+
});
51+
if (res.statusCode >= 400) {
52+
const text = await res.body.text();
53+
throw new yookassa_error_1.YookassaError('yookassa_error', text, text);
5054
}
51-
const res = await (0, rxjs_1.firstValueFrom)(this.httpService.request(options));
52-
return res.data;
55+
return (await res.body.json());
5356
}
5457
catch (error) {
55-
throw new yookassa_error_1.YookassaError(((_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.type) || 'yookassa_error', ((_d = (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.description) || error.message, (_e = error === null || error === void 0 ? void 0 : error.response) === null || _e === void 0 ? void 0 : _e.data);
58+
throw new yookassa_error_1.YookassaError((error === null || error === void 0 ? void 0 : error.type) || 'yookassa_error', (error === null || error === void 0 ? void 0 : error.message) || 'Unknown Yookassa error', error);
5659
}
5760
}
5861
get(url, params) {
@@ -61,6 +64,26 @@ let YookassaHttpClient = class YookassaHttpClient {
6164
post(url, data) {
6265
return this.request({ method: 'POST', url, data });
6366
}
67+
buildAuthHeader() {
68+
const creds = Buffer.from(`${this.config.shopId}:${this.config.apiKey}`).toString('base64');
69+
return `Basic ${creds}`;
70+
}
71+
buildUrl(url, params) {
72+
let full = `${yookassa_constants_1.YOOKASSA_API_URL}${url}`;
73+
if (params && typeof params === 'object') {
74+
const qp = new URLSearchParams(params);
75+
full += `?${qp.toString()}`;
76+
}
77+
return full;
78+
}
79+
extractProxyFromAgent() {
80+
var _a, _b;
81+
const proxy = (_b = (_a = this.config.agent) === null || _a === void 0 ? void 0 : _a.proxy) === null || _b === void 0 ? void 0 : _b.href;
82+
if (!proxy) {
83+
throw new Error('[YooKassa] Unable to extract proxy URL from HttpsProxyAgent');
84+
}
85+
return proxy;
86+
}
6487
};
6588
exports.YookassaHttpClient = YookassaHttpClient;
6689
exports.YookassaHttpClient = YookassaHttpClient = __decorate([

packages/nestjs-yookassa/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nestjs-yookassa",
3-
"version": "2.1.3",
3+
"version": "2.1.4",
44
"description": "A NestJS library for integrating with YooKassa API",
55
"keywords": [
66
"nest",
@@ -40,8 +40,9 @@
4040
"dependencies": {
4141
"@nestjs/axios": "^4.0.1",
4242
"axios": "^1.11.0",
43+
"https-proxy-agent": "^7.0.6",
4344
"rxjs": "^7.8.2",
44-
"https-proxy-agent": "^7.0.6"
45+
"undici": "^7.16.0"
4546
},
4647
"husky": {
4748
"hooks": {

packages/nestjs-yookassa/src/core/http/yookassa.http-client.ts

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,57 +9,61 @@ import {
99
type YookassaModuleOptions,
1010
YookassaOptionsSymbol
1111
} from '../../common/interfaces'
12+
import { request, Agent, ProxyAgent } from 'undici'
1213

1314
@Injectable()
1415
export class YookassaHttpClient {
16+
private readonly dispatcher: any
17+
1518
public constructor(
1619
@Inject(YookassaOptionsSymbol)
1720
private readonly config: YookassaModuleOptions,
1821
private readonly httpService: HttpService
1922
) {
20-
const client = this.httpService.axiosRef
21-
22-
client.defaults.baseURL = YOOKASSA_API_URL
23-
client.defaults.timeout = 15000
24-
25-
client.defaults.auth = {
26-
username: this.config.shopId,
27-
password: this.config.apiKey
28-
}
29-
30-
client.defaults.headers.common['Content-Type'] = 'application/json'
31-
32-
client.defaults.proxy = false
33-
3423
if (this.config.agent) {
35-
client.defaults.httpAgent = this.config.agent
36-
client.defaults.httpsAgent = this.config.agent
24+
const proxyUrl = this.extractProxyFromAgent()
3725

38-
console.log(`[YooKassa] Proxy agent enabled`)
26+
this.dispatcher = new ProxyAgent(proxyUrl)
27+
28+
console.log('[YooKassa] ProxyAgent enabled:', proxyUrl)
29+
} else {
30+
this.dispatcher = undefined
3931
}
4032
}
4133

42-
public async request<T = any>(options: AxiosRequestConfig): Promise<T> {
34+
public async request<T = any>(options: {
35+
method: string
36+
url: string
37+
data?: any
38+
params?: any
39+
}): Promise<T> {
40+
const url = this.buildUrl(options.url, options.params)
41+
4342
try {
44-
options.headers = {
45-
...options.headers,
46-
'Idempotence-Key': randomUUID()
47-
}
43+
const res = await request(url, {
44+
method: options.method,
45+
dispatcher: this.dispatcher,
46+
headersTimeout: 15000,
47+
bodyTimeout: 15000,
48+
headers: {
49+
'Content-Type': 'application/json',
50+
'Idempotence-Key': randomUUID(),
51+
Authorization: this.buildAuthHeader()
52+
},
53+
body: options.data ? JSON.stringify(options.data) : undefined
54+
})
4855

49-
if (this.config.agent) {
50-
options.httpAgent = this.config.agent
51-
options.httpsAgent = this.config.agent
52-
options.proxy = false
56+
if (res.statusCode >= 400) {
57+
const text = await res.body.text()
58+
throw new YookassaError('yookassa_error', text, text)
5359
}
5460

55-
const res = await firstValueFrom(this.httpService.request(options))
56-
57-
return res.data
61+
return (await res.body.json()) as T
5862
} catch (error: any) {
5963
throw new YookassaError(
60-
error?.response?.data?.type || 'yookassa_error',
61-
error?.response?.data?.description || error.message,
62-
error?.response?.data
64+
error?.type || 'yookassa_error',
65+
error?.message || 'Unknown Yookassa error',
66+
error
6367
)
6468
}
6569
}
@@ -71,4 +75,35 @@ export class YookassaHttpClient {
7175
public post<T>(url: string, data?: any) {
7276
return this.request<T>({ method: 'POST', url, data })
7377
}
78+
79+
private buildAuthHeader() {
80+
const creds = Buffer.from(
81+
`${this.config.shopId}:${this.config.apiKey}`
82+
).toString('base64')
83+
84+
return `Basic ${creds}`
85+
}
86+
87+
private buildUrl(url: string, params?: any): string {
88+
let full = `${YOOKASSA_API_URL}${url}`
89+
90+
if (params && typeof params === 'object') {
91+
const qp = new URLSearchParams(params)
92+
full += `?${qp.toString()}`
93+
}
94+
95+
return full
96+
}
97+
98+
private extractProxyFromAgent(): string {
99+
const proxy = this.config.agent?.proxy?.href
100+
101+
if (!proxy) {
102+
throw new Error(
103+
'[YooKassa] Unable to extract proxy URL from HttpsProxyAgent'
104+
)
105+
}
106+
107+
return proxy
108+
}
74109
}

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)