Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/plugin-protocol-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"browser": {
"lib/fetch.js": "./lib/fetch.browser.js",
"./lib/fetch.js": "./lib/fetch.browser.js",
"./lib/index.es.js": "./lib/index.browser.js"
},
"sideEffects": false,
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-protocol-http/src/fetch.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ const fetch = (...args) => {

const { Headers, Request, Response } = glob;

export { fetch, Headers, Request, Response };
class HttpAgent {}
class HttpsAgent {}

export { fetch, Headers, Request, Response, HttpAgent, HttpsAgent};
4 changes: 3 additions & 1 deletion packages/plugin-protocol-http/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { RequestInit, RequestInfo, fetch as undiciFetch } from 'undici';
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';

const fetch = (input: RequestInfo, init?: RequestInit) => {
return undiciFetch(input, init);
};

const { Headers, Request, Response } = globalThis;

export { fetch, Headers, Request, Response };
export { fetch, Headers, Request, Response, HttpAgent, HttpsAgent };
107 changes: 95 additions & 12 deletions packages/plugin-protocol-http/src/http.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ jest.mock('undici', () => {

import { Context, Status } from '@tinkoff/request-core';
import http from './http';
import https from 'https';
import { Agent, EnvHttpProxyAgent } from 'undici';

const plugin = http();
const next = jest.fn();
Expand Down Expand Up @@ -162,13 +164,9 @@ describe('plugins/http', () => {
});
(fetch as any).mockReturnValueOnce(mockResponse);

class MockedAgent {
dispatch() {}
}
const mockedAgent = new Agent();

const mockedAgent = new MockedAgent();

http({ agent: { http: mockedAgent as any, https: undefined as any } }).init?.(
http({ agent: { http: mockedAgent, https: undefined as any } }).init?.(
new Context({
request: {
url: 'http://test.com/api',
Expand Down Expand Up @@ -202,6 +200,50 @@ describe('plugins/http', () => {
});
});

it('request with wrong https agent', async () => {
const body = { a: 3 };
const mockResponse = createResponse(body, {
headers: {
'Content-type': 'application/json;',
},
});
(fetch as any).mockReturnValueOnce(mockResponse);

const mockedAgent = new https.Agent();

http({ agent: { http: undefined as any, https: mockedAgent as any } }).init?.(
new Context({
request: {
url: 'https://test.com/api',
headers: {
'Content-type': 'application/json',
},
},
}),
next,
null as any
);

await new Promise((res) => {
next.mockImplementation(res);
});

expect(fetch as any).toHaveBeenCalledWith('https://test.com/api', {
method: 'GET',
credentials: 'same-origin',
body: undefined,
headers: {
'Content-type': 'application/json',
},
signal: expect.anything(),
});

expect(next).toHaveBeenLastCalledWith({
response: body,
status: Status.COMPLETE,
});
});

it('request with custom https agent', async () => {
const body = { a: 3 };
const mockResponse = createResponse(body, {
Expand All @@ -211,13 +253,9 @@ describe('plugins/http', () => {
});
(fetch as any).mockReturnValueOnce(mockResponse);

class MockedAgent {
dispatch() {}
}

const mockedAgent = new MockedAgent();
const mockedAgent = new Agent();

http({ agent: { http: undefined as any, https: mockedAgent as any } }).init?.(
http({ agent: { http: undefined as any, https: mockedAgent } }).init?.(
new Context({
request: {
url: 'https://test.com/api',
Expand Down Expand Up @@ -251,6 +289,51 @@ describe('plugins/http', () => {
});
});

it('request with custom EnvHttpProxyAgent agent', async () => {
const body = { a: 3 };
const mockResponse = createResponse(body, {
headers: {
'Content-type': 'application/json',
},
});
(fetch as any).mockReturnValueOnce(mockResponse);

const mockedAgent = new EnvHttpProxyAgent();

http({ agent: { http: mockedAgent, https: undefined as any } }).init?.(
new Context({
request: {
url: 'http://test.com/api',
headers: {
'Content-type': 'application/json',
},
},
}),
next,
null as any
);

await new Promise((res) => {
next.mockImplementation(res);
});

expect(fetch as any).toHaveBeenCalledWith('http://test.com/api', {
dispatcher: mockedAgent,
method: 'GET',
credentials: 'same-origin',
body: undefined,
headers: {
'Content-type': 'application/json',
},
signal: expect.anything(),
});

expect(next).toHaveBeenLastCalledWith({
response: body,
status: Status.COMPLETE,
});
});

it('request with custom querySerializer', async () => {
const body = { a: 3 };
const mockResponse = createResponse(body, {
Expand Down
28 changes: 23 additions & 5 deletions packages/plugin-protocol-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Plugin, Status } from '@tinkoff/request-core';
import { Query, QuerySerializer } from '@tinkoff/request-url-utils';
import { addQuery, normalizeUrl } from '@tinkoff/request-url-utils';

import { fetch } from './fetch';
import { fetch, HttpAgent, HttpsAgent } from './fetch';
import { serialize } from './serialize';
import { PROTOCOL_HTTP, REQUEST_TYPES, HttpMethods } from './constants';
import parse from './parse';
import createForm from './form';
import { TimeoutError, AbortError, HttpRequestError } from './errors';
import type { Agent } from 'undici/types';
import type { Dispatcher } from 'undici/types';

declare module '@tinkoff/request-core/lib/types.h' {
export interface Request {
Expand Down Expand Up @@ -49,6 +49,14 @@ declare module '@tinkoff/request-core/lib/types.h' {
}

const isBrowser = typeof window !== 'undefined';
const showWarn = (agentType: 'http' | 'https') =>
console.warn(
`Starting from 0.15.0 plugin-protocol-http use undici fetch and corresponding Agent\nProvided ${agentType} Agent from node:${agentType} module, so it will be ignored`
);

function isProvidedAgentBelongsNode(agent: HttpAgent | HttpsAgent | Dispatcher): boolean {
return agent instanceof HttpAgent || agent instanceof HttpsAgent;
}

/**
* Makes http/https request.
Expand All @@ -67,29 +75,39 @@ const isBrowser = typeof window !== 'undefined';
* abortPromise {Promise}
* signal {AbortSignal}
*
* @param {agent} [agent = Agent] set custom agent for fetch in node js. The browser ignores this parameter.
* @param {agent} agent set custom agent for fetch in node js. The browser ignores this parameter.
* @param {QuerySerializer} querySerializer function that will be used instead of default value to serialize query strings in url
* @return {{init: init}}
*/
export default ({
agent,
querySerializer,
}: {
agent?: { http: Agent; https: Agent };
agent?: { http: Dispatcher; https: Dispatcher };
querySerializer?: QuerySerializer;
} = {}): Plugin => {
let customAgent: (url: string) => undefined | Agent = () => undefined;
let customAgent: (url: string) => undefined | Dispatcher = () => undefined;

if (!isBrowser && agent) {
customAgent = (url) => {
try {
const parsedUrl = new URL(url);

if (parsedUrl.protocol === 'http:') {
if (isProvidedAgentBelongsNode(agent.http)) {
showWarn('http');
return undefined;
}

return agent.http;
}
} catch (_err) {}

if (isProvidedAgentBelongsNode(agent.https)) {
showWarn('https');
return undefined;
}

return agent.https;
};
}
Expand Down