forked from MrRefactoring/jira.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbaseClient.ts
165 lines (134 loc) · 5.32 KB
/
baseClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import type { Callback } from '../callback';
import type { Client } from './client';
import type { Config } from '../config';
import { getAuthenticationToken } from '../services/authenticationService';
import type { RequestConfig } from '../requestConfig';
import { HttpException, isObject } from './httpException';
const STRICT_GDPR_FLAG = 'x-atlassian-force-account-id';
const ATLASSIAN_TOKEN_CHECK_FLAG = 'X-Atlassian-Token';
const ATLASSIAN_TOKEN_CHECK_NOCHECK_VALUE = 'no-check';
export class BaseClient implements Client {
private instance: AxiosInstance;
constructor(protected readonly config: Config) {
try {
// eslint-disable-next-line no-new
new URL(config.host);
} catch (e) {
throw new Error(
"Couldn't parse the host URL. Perhaps you forgot to add 'http://' or 'https://' at the beginning of the URL?",
);
}
this.instance = axios.create({
paramsSerializer: this.paramSerializer.bind(this),
...config.baseRequestConfig,
baseURL: config.host,
headers: this.removeUndefinedProperties({
[STRICT_GDPR_FLAG]: config.strictGDPR,
[ATLASSIAN_TOKEN_CHECK_FLAG]: config.noCheckAtlassianToken ? ATLASSIAN_TOKEN_CHECK_NOCHECK_VALUE : undefined,
...config.baseRequestConfig?.headers,
}),
});
}
protected paramSerializer(parameters: Record<string, any>): string {
const parts: string[] = [];
Object.entries(parameters).forEach(([key, value]) => {
if (value === null || typeof value === 'undefined') {
return;
}
if (Array.isArray(value)) {
// eslint-disable-next-line no-param-reassign
value = value.join(',');
}
if (value instanceof Date) {
// eslint-disable-next-line no-param-reassign
value = value.toISOString();
} else if (value !== null && typeof value === 'object') {
// eslint-disable-next-line no-param-reassign
value = JSON.stringify(value);
} else if (value instanceof Function) {
const part = value();
// eslint-disable-next-line consistent-return
return part && parts.push(part);
}
parts.push(`${this.encode(key)}=${this.encode(value)}`);
});
return parts.join('&');
}
protected encode(value: string) {
return encodeURIComponent(value)
.replace(/%3A/gi, ':')
.replace(/%24/g, '$')
.replace(/%2C/gi, ',')
.replace(/%20/g, '+')
.replace(/%5B/gi, '[')
.replace(/%5D/gi, ']');
}
protected removeUndefinedProperties(obj: Record<string, any>): Record<string, any> {
return Object.entries(obj)
.filter(([, value]) => typeof value !== 'undefined')
.reduce((accumulator, [key, value]) => ({ ...accumulator, [key]: value }), {});
}
async sendRequest<T>(requestConfig: RequestConfig, callback: never, telemetryData?: any): Promise<T>;
async sendRequest<T>(requestConfig: RequestConfig, callback: Callback<T>, telemetryData?: any): Promise<void>;
async sendRequest<T>(requestConfig: RequestConfig, callback: Callback<T> | never): Promise<void | T> {
try {
const response = await this.sendRequestFullResponse<T>(requestConfig);
return this.handleSuccessResponse(response.data, callback);
} catch (e: unknown) {
return this.handleFailedResponse(e, callback);
}
}
async sendRequestFullResponse<T>(requestConfig: RequestConfig): Promise<AxiosResponse<T>> {
const modifiedRequestConfig = {
...requestConfig,
headers: this.removeUndefinedProperties({
Authorization: await getAuthenticationToken(this.config.authentication),
...requestConfig.headers,
}),
};
return this.instance.request<T>(modifiedRequestConfig);
}
handleSuccessResponse<T>(response: any, callback?: Callback<T> | never): T | void {
const callbackResponseHandler = callback && ((data: T): void => callback(null, data));
const defaultResponseHandler = (data: T): T => data;
const responseHandler = callbackResponseHandler ?? defaultResponseHandler;
this.config.middlewares?.onResponse?.(response.data);
return responseHandler(response);
}
handleFailedResponse<T>(e: unknown, callback?: Callback<T> | never): void {
const err = this.buildErrorHandlingResponse(e);
const callbackErrorHandler = callback && ((error: Config.Error) => callback(error));
const defaultErrorHandler = (error: Config.Error) => {
throw error;
};
const errorHandler = callbackErrorHandler ?? defaultErrorHandler;
this.config.middlewares?.onError?.(err);
return errorHandler(err);
}
private buildErrorHandlingResponse(e: unknown): Config.Error {
if (axios.isAxiosError(e) && e.response) {
return new HttpException(
{
code: e.code,
message: e.message,
data: e.response.data,
status: e.response?.status,
statusText: e.response?.statusText,
},
e.response.status,
{ cause: e },
);
}
if (axios.isAxiosError(e)) {
return e;
}
if (isObject(e) && isObject((e as Record<string, any>).response)) {
return new HttpException((e as Record<string, any>).response);
}
if (e instanceof Error) {
return new HttpException(e);
}
return new HttpException('Unknown error occurred.', 500, { cause: e });
}
}