1 | import axios, {
|
2 | AxiosInstance,
|
3 | AxiosError,
|
4 | AxiosResponse,
|
5 | AxiosRequestConfig,
|
6 | } from "axios";
|
7 | import { Readable } from "stream";
|
8 | import { HTTPError, ReadError, RequestError } from "./exceptions";
|
9 | import * as fileType from "file-type";
|
10 | import * as qs from "querystring";
|
11 |
|
12 | const pkg = require("../package.json");
|
13 |
|
14 | interface httpClientConfig extends Partial<AxiosRequestConfig> {
|
15 | baseURL?: string;
|
16 | defaultHeaders?: any;
|
17 | responseParser?: <T>(res: AxiosResponse) => T;
|
18 | }
|
19 |
|
20 | export 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 |