UNPKG

5.14 kBPlain TextView Raw
1import { Buffer } from "node:buffer";
2import { Readable } from "node:stream";
3import { HTTPFetchError } from "./exceptions.js";
4import { USER_AGENT } from "./version.js";
5
6export interface FetchRequestConfig {
7 headers?: Record<string, string>;
8}
9
10interface httpFetchClientConfig {
11 baseURL: string;
12 defaultHeaders: Record<string, string>;
13}
14
15export function convertResponseToReadable(response: Response): Readable {
16 const reader = response.body.getReader();
17 return new Readable({
18 async read() {
19 const { done, value } = await reader.read();
20 if (done) {
21 this.push(null);
22 } else {
23 this.push(Buffer.from(value));
24 }
25 },
26 });
27}
28
29export default class HTTPFetchClient {
30 private readonly baseURL: string;
31 private readonly defaultHeaders: Record<string, string>;
32
33 constructor(config: httpFetchClientConfig) {
34 this.baseURL = config.baseURL;
35 this.defaultHeaders = {
36 "User-Agent": USER_AGENT,
37 ...config.defaultHeaders,
38 };
39 }
40
41 public async get<T>(url: string, params?: any): Promise<Response> {
42 const requestUrl = new URL(url, this.baseURL);
43 if (params) {
44 const searchParams = new URLSearchParams();
45 for (const key in params) {
46 if (params.hasOwnProperty(key)) {
47 searchParams.append(key, params[key]);
48 }
49 }
50 requestUrl.search = searchParams.toString();
51 }
52 const response = await fetch(requestUrl, {
53 headers: this.defaultHeaders,
54 });
55 await this.checkResponseStatus(response);
56 return response;
57 }
58
59 public async post(
60 url: string,
61 body?: any,
62 config?: Partial<FetchRequestConfig>,
63 ): Promise<Response> {
64 const requestUrl = new URL(url, this.baseURL);
65 const response = await fetch(requestUrl, {
66 method: "POST",
67 headers: {
68 "Content-Type": "application/json",
69 ...this.defaultHeaders,
70 ...(config && config.headers),
71 },
72 body: JSON.stringify(body),
73 });
74 await this.checkResponseStatus(response);
75 return response;
76 }
77
78 public async put(
79 url: string,
80 body?: any,
81 config?: Partial<FetchRequestConfig>,
82 ): Promise<Response> {
83 const requestUrl = new URL(url, this.baseURL);
84 const response = await fetch(requestUrl, {
85 method: "PUT",
86 headers: {
87 "Content-Type": "application/json",
88 ...this.defaultHeaders,
89 ...(config && config.headers),
90 },
91 body: JSON.stringify(body),
92 });
93 await this.checkResponseStatus(response);
94 return response;
95 }
96
97 public async postForm(url: string, body?: any): Promise<Response> {
98 const requestUrl = new URL(url, this.baseURL);
99 const params = new URLSearchParams();
100 for (const key in body) {
101 if (body.hasOwnProperty(key)) {
102 params.append(key, body[key]);
103 }
104 }
105 const response = await fetch(requestUrl, {
106 method: "POST",
107 headers: {
108 "Content-Type": "application/x-www-form-urlencoded",
109 ...this.defaultHeaders,
110 },
111 body: params.toString(),
112 });
113 await this.checkResponseStatus(response);
114 return response;
115 }
116
117 public async postFormMultipart(
118 url: string,
119 form: FormData,
120 ): Promise<Response> {
121 const requestUrl = new URL(url, this.baseURL);
122 const response = await fetch(requestUrl, {
123 method: "POST",
124 headers: {
125 ...this.defaultHeaders,
126 },
127 body: form,
128 });
129 await this.checkResponseStatus(response);
130 return response;
131 }
132
133 public async putFormMultipart(
134 url: string,
135 form: FormData,
136 config?: Partial<FetchRequestConfig>,
137 ): Promise<Response> {
138 const requestUrl = new URL(url, this.baseURL);
139 const response = await fetch(requestUrl, {
140 method: "PUT",
141 headers: {
142 ...this.defaultHeaders,
143 ...(config && (config.headers ? config.headers : {})),
144 },
145 body: form,
146 });
147 await this.checkResponseStatus(response);
148 return response;
149 }
150 public async postBinaryContent(url: string, body: Blob): Promise<Response> {
151 const requestUrl = new URL(url, this.baseURL);
152 const response = await fetch(requestUrl, {
153 method: "POST",
154 headers: {
155 "Content-Type": body.type,
156 ...this.defaultHeaders,
157 },
158 body: body,
159 });
160 await this.checkResponseStatus(response);
161 return response;
162 }
163
164 public async delete(url: string, params?: any): Promise<Response> {
165 const requestUrl = new URL(url, this.baseURL);
166 if (params) {
167 requestUrl.search = new URLSearchParams(params).toString();
168 }
169 const response = await fetch(requestUrl, {
170 method: "DELETE",
171 headers: {
172 ...this.defaultHeaders,
173 },
174 });
175 await this.checkResponseStatus(response);
176 return response;
177 }
178
179 private async checkResponseStatus(response: Response) {
180 const { ok, status, statusText, headers } = response;
181 const message = `${status} - ${statusText}`;
182
183 if (!ok) {
184 const body = await response.text();
185
186 throw new HTTPFetchError(message, {
187 status,
188 statusText,
189 headers,
190 body,
191 });
192 }
193 }
194}