UNPKG

4.06 kBPlain TextView Raw
1import axios, {
2 AxiosInstance,
3 AxiosError,
4 AxiosResponse,
5 AxiosRequestConfig,
6} from "axios";
7import { Readable } from "stream";
8import { HTTPError, ReadError, RequestError } from "./exceptions";
9import * as fileType from "file-type";
10import * as qs from "querystring";
11
12const pkg = require("../package.json");
13
14interface httpClientConfig extends Partial<AxiosRequestConfig> {
15 baseURL?: string;
16 defaultHeaders?: any;
17 responseParser?: <T>(res: AxiosResponse) => T;
18}
19
20export default class HTTPClient {
21 private instance: AxiosInstance;
22 private config: httpClientConfig;
23
24 constructor(config: httpClientConfig = {}) {
25 this.config = config;
26 const { baseURL, defaultHeaders } = config;
27 this.instance = axios.create({
28 baseURL,
29 headers: Object.assign({}, defaultHeaders, {
30 "User-Agent": `${pkg.name}/${pkg.version}`,
31 }),
32 });
33
34 this.instance.interceptors.response.use(
35 res => res,
36 err => Promise.reject(this.wrapError(err)),
37 );
38 }
39
40 public async get<T>(url: string, params?: any): Promise<T> {
41 const res = await this.instance.get(url, { params });
42 return res.data;
43 }
44
45 public async getStream(url: string, params?: any): Promise<Readable> {
46 const res = await this.instance.get(url, {
47 params,
48 responseType: "stream",
49 });
50 return res.data as Readable;
51 }
52
53 public async post<T>(
54 url: string,
55 body?: any,
56 config?: Partial<AxiosRequestConfig>,
57 ): Promise<T> {
58 const res = await this.instance.post(url, body, {
59 headers: {
60 "Content-Type": "application/json",
61 ...(config && config.headers),
62 },
63 ...config,
64 });
65
66 return this.responseParse(res);
67 }
68
69 private responseParse(res: AxiosResponse) {
70 const { responseParser } = this.config;
71 if (responseParser) return responseParser(res);
72 else return res.data;
73 }
74
75 public async put<T>(
76 url: string,
77 body?: any,
78 config?: Partial<AxiosRequestConfig>,
79 ): Promise<T> {
80 const res = await this.instance.put(url, body, {
81 headers: {
82 "Content-Type": "application/json",
83 ...(config && config.headers),
84 },
85 ...config,
86 });
87
88 return this.responseParse(res);
89 }
90
91 public async postForm<T>(url: string, body?: any): Promise<T> {
92 const res = await this.instance.post(url, qs.stringify(body), {
93 headers: { "Content-Type": "application/x-www-form-urlencoded" },
94 });
95
96 return res.data;
97 }
98
99 public async toBuffer(data: Buffer | Readable) {
100 if (Buffer.isBuffer(data)) {
101 return data;
102 } else if (data instanceof Readable) {
103 return await new Promise<Buffer>((resolve, reject) => {
104 const buffers: Buffer[] = [];
105 let size = 0;
106 data.on("data", (chunk: Buffer) => {
107 buffers.push(chunk);
108 size += chunk.length;
109 });
110 data.on("end", () => resolve(Buffer.concat(buffers, size)));
111 data.on("error", reject);
112 });
113 } else {
114 throw new Error("invalid data type for binary data");
115 }
116 }
117
118 public async postBinary<T>(
119 url: string,
120 data: Buffer | Readable,
121 contentType?: string,
122 ): Promise<T> {
123 const buffer = await this.toBuffer(data);
124
125 const res = await this.instance.post(url, buffer, {
126 headers: {
127 "Content-Type": contentType || (await fileType.fromBuffer(buffer)).mime,
128 "Content-Length": buffer.length,
129 },
130 });
131
132 return res.data;
133 }
134
135 public async delete<T>(url: string, params?: any): Promise<T> {
136 const res = await this.instance.delete(url, { params });
137 return res.data;
138 }
139
140 private wrapError(err: AxiosError): Error {
141 if (err.response) {
142 return new HTTPError(
143 err.message,
144 err.response.status,
145 err.response.statusText,
146 err,
147 );
148 } else if (err.code) {
149 return new RequestError(err.message, err.code, err);
150 } else if (err.config) {
151 // unknown, but from axios
152 return new ReadError(err);
153 }
154
155 // otherwise, just rethrow
156 return err;
157 }
158}
159
\No newline at end of file