@pushwoosh/http-client 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +5 -0
- package/.dockerignore +2 -0
- package/.env.example +3 -0
- package/.eslintignore +3 -0
- package/.eslintrc +38 -0
- package/.gitlab-ci.yml +122 -0
- package/README.md +251 -181
- package/babel.config.js +24 -0
- package/jest.config.js +3 -0
- package/package.json +32 -38
- package/src/http-client.errors.ts +324 -0
- package/src/http-client.main.ts +451 -0
- package/src/http-client.types.ts +102 -0
- package/src/http-client.utilities.ts +84 -0
- package/src/index.ts +4 -0
- package/tsconfig.eslint.json +9 -0
- package/tsconfig.json +51 -0
- package/webpack.base.config.js +40 -0
- package/webpack.development.config.js +33 -0
- package/webpack.production.config.js +24 -0
- package/webpack.utilities.js +12 -0
- package/dist/http-client.d.ts +0 -171
- package/dist/http-client.errors.d.ts +0 -149
- package/dist/http-client.helpers.d.ts +0 -2
- package/dist/http-client.types.d.ts +0 -118
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,234 +1,304 @@
|
|
|
1
1
|
# Pushwoosh / Micro Frontends / Http Client
|
|
2
2
|
Этот пакет является частью микрофронтендовой архитектуры
|
|
3
3
|
<br/>
|
|
4
|
-
|
|
5
|
-
<br/>
|
|
6
|
-
Предназначен для выполнения запросов к API
|
|
7
|
-
|
|
8
|
-
## Термины
|
|
9
|
-
[Аутентификация](https://auth0.com/docs/authentication) - процесс подтверждения личности пользователя
|
|
10
|
-
|
|
11
|
-
[Авторизация](https://auth0.com/docs/authorization) - процесс проверки доступа к защищенным ресурсам
|
|
12
|
-
|
|
13
|
-
[Клиент](https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1) - приложение запрашивающее защищенные ресурсы от имени пользователя
|
|
14
|
-
|
|
15
|
-
[Сервер авторизации](https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1) - сервер, выдающий токены доступа клиенту после успешной аутентификации владельца ресурса и получение авторизации
|
|
4
|
+
Предназначен для выполнения запросов к API с использованием OAuth2 протокола
|
|
16
5
|
|
|
17
6
|
## Установка
|
|
18
|
-
1. Сгенерировать ключи для запуска сервера в режиме
|
|
7
|
+
1. Сгенерировать ключи для запуска сервера в режиме
|
|
8
|
+
HTTPS ([Confluence](https://confluence.corp.pushwoosh.com/display/FD/HTTPS+on+localhost))
|
|
19
9
|
2. Выполнить `npm ci`
|
|
20
10
|
|
|
21
11
|
## Запуск разработки
|
|
22
|
-
1. Скопировать файл `.env.example` в `.env
|
|
12
|
+
1. Скопировать файл `.env.example` в `.env`
|
|
23
13
|
2. Выполнить `npm start`
|
|
24
14
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
## Использование
|
|
16
|
+
Работу с Http Client можно разделить на несколько условных частей:
|
|
17
|
+
|
|
18
|
+
1. Создание экземпляра
|
|
19
|
+
2. Авторизация (этот шаг может быть пропущен)
|
|
20
|
+
3. Выполнение запросов
|
|
21
|
+
4. Обработка ответов
|
|
22
|
+
5. Обработка ошибок
|
|
29
23
|
|
|
30
|
-
### Создание экземпляра
|
|
31
|
-
|
|
24
|
+
### Создание экземпляра
|
|
25
|
+
Первым делом нужно создать экземпляр HttpClient
|
|
32
26
|
|
|
33
27
|
```typescript
|
|
34
|
-
|
|
28
|
+
const httpClient = new HttpClient(history, config);
|
|
29
|
+
```
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
Где:
|
|
32
|
+
<br/>
|
|
33
|
+
<strong>history</strong> - API для работы с навигацией
|
|
34
|
+
<br/>
|
|
35
|
+
<strong>config</strong> - конфигурация Http Client
|
|
36
|
+
<br/>
|
|
37
|
+
<strong>config.defaultResponseMiddleware</strong> - обработчик ответа от сервера по умолчанию (см. раздел обработка ответов от сервера)
|
|
38
|
+
<br/>
|
|
39
|
+
<strong>config.defaultErrorMiddleware</strong> - обработчик ошибок по умолчанию (см. раздел обработка ошибок)
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createBrowserHistory } from 'history';
|
|
43
|
+
import { defaultErrorMiddleware, defaultResponseMiddleware, HttpClient } from '@pushwoosh/http-client';
|
|
44
|
+
|
|
45
|
+
// создаем API для работы с навигацией
|
|
46
|
+
const history = createBrowserHistory();
|
|
47
|
+
|
|
48
|
+
// формируем конфигурацию Http Client
|
|
49
|
+
const config = {
|
|
50
|
+
// обязательное поле: описывает адреса для выполнения внутренних запросов HttpClient к серверу авторизации
|
|
51
|
+
// для более подробной инфорации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1 - authorization server
|
|
40
52
|
urls: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
// обязательное поле: адрес для получения authorization code и authorization state
|
|
54
|
+
// для более подробноей информации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.1
|
|
55
|
+
authorize: 'https://sso.pushwoosh.com/authorize',
|
|
56
|
+
// обязательное поле: адрес для получения access token и refresh token
|
|
57
|
+
token: 'https://sso.pushwoosh.com/token',
|
|
58
|
+
// обязательное поле: адрес для завершения сессии
|
|
59
|
+
logout: 'https://sso.pushwoosh.com/logout',
|
|
44
60
|
},
|
|
45
|
-
|
|
61
|
+
// обязательное поле: идентификатор клиента, свзязан с сервером SSO, зависит от домена на котором работает HttpClient
|
|
62
|
+
// для более подробной инфорации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1 - client
|
|
63
|
+
clientId: 'APP_FRONT',
|
|
64
|
+
// обязательное поле: область ограничений на работу с защищенными ресурсами
|
|
65
|
+
// для более подробной инфорации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1 - resource server
|
|
66
|
+
scope: 'cp-pushwoosh customer-journey',
|
|
67
|
+
// обязательное поле: параметры куки для сохранения authorization state и url before authorize
|
|
46
68
|
cookie: {
|
|
47
|
-
|
|
69
|
+
// обязательное поле: определяет домен на котором будут доступны куки
|
|
70
|
+
domain: '.pushwoosh.com',
|
|
71
|
+
// определяет время жизни кук
|
|
72
|
+
expires: 300,
|
|
73
|
+
// определяет путь, где будут доступны куки
|
|
74
|
+
path: '/',
|
|
75
|
+
// определяет будут ли передаваться куки только по https протоколу
|
|
76
|
+
secure: true,
|
|
77
|
+
// определяет при каких условиях куки будут отправлятся на сервер при выполнении кроссдоменных запросов
|
|
78
|
+
sameSite: 'none',
|
|
48
79
|
},
|
|
49
|
-
|
|
80
|
+
// обязательное поле: бызовый обработчик ответов от сервера
|
|
81
|
+
basicResponseMiddleware: defaultResponseMiddleware,
|
|
82
|
+
// обязательное поле: базовый обработчик ошибкок при выполнении запросов
|
|
83
|
+
basicErrorMiddleware: defaultErrorMiddleware,
|
|
84
|
+
};
|
|
50
85
|
```
|
|
51
86
|
|
|
52
|
-
###
|
|
53
|
-
|
|
87
|
+
### Авторизация
|
|
88
|
+
Этот шаг может быть пропущен, если access token получается другим способом.
|
|
89
|
+
<br/>
|
|
90
|
+
Если требуется произвести авторизацию клиента с помощью SSO, то нам нужно выполнить 2 действия:
|
|
91
|
+
<br/>
|
|
92
|
+
|
|
93
|
+
1. Получить Basic Authorization путем перехода на сервер авторизации и возвращения обратно с authorization code и authorization state
|
|
94
|
+
```typescript
|
|
95
|
+
await httpClient.login();
|
|
96
|
+
```
|
|
97
|
+
2. Получить Bearer Authorization путем выполнения запроса к серверу авторизации и получения access token и refresh token с помощью authorization code
|
|
98
|
+
```typescript
|
|
99
|
+
const introspect = await httpClient.authorize(authorization);
|
|
100
|
+
```
|
|
54
101
|
|
|
102
|
+
Полный пример выполнения авторизации клиента:
|
|
55
103
|
```typescript
|
|
56
|
-
|
|
104
|
+
// получаем authorization code и authorization state из строки браузера
|
|
105
|
+
const query = parse(window.location.search.slice(1));
|
|
106
|
+
const authorizationCode = typeof query.code === 'string' ? query.code : null;
|
|
107
|
+
const authorizationState = typeof query.state === 'string' ? query.state : null;
|
|
108
|
+
const authorization = authorizationCode && authorizationState
|
|
109
|
+
? { code: authorizationCode, state: authorizationState }
|
|
110
|
+
: null;
|
|
111
|
+
|
|
112
|
+
// выполняем авторизацию
|
|
113
|
+
if (authorization) {
|
|
114
|
+
const introspect = await httpClient.authorize(authorization);
|
|
115
|
+
if (!introspect) {
|
|
116
|
+
await httpClient.login();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
57
120
|
```
|
|
58
121
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`authorizationState` - это случайная строка, сгенерированная и сохраненная клиентом перед началом сеанса, возвращается вместе с `authorizationCode`
|
|
62
|
-
|
|
63
|
-
`introspect` - это данные о текущем сеансе (идентификатор пользователя, идентификатор аккаунта и т.п.), которые зашиты в `access token`
|
|
64
|
-
|
|
65
|
-
### Начать сеанс
|
|
66
|
-
Выполнить аутентификацию пользователя на клиенте
|
|
67
|
-
|
|
122
|
+
### Выполнение запросов:
|
|
123
|
+
Для выполнения запросов можно использовать следующий метод:
|
|
68
124
|
```typescript
|
|
69
|
-
|
|
70
|
-
```
|
|
125
|
+
import { defaultErrorMiddleware } from './http-client.utilities';
|
|
71
126
|
|
|
72
|
-
|
|
73
|
-
|
|
127
|
+
type Result = {
|
|
128
|
+
readonly data1: string;
|
|
129
|
+
readonly data2: number;
|
|
130
|
+
};
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
132
|
+
type Params = {
|
|
133
|
+
readonly param1: string;
|
|
134
|
+
readonly param2: number;
|
|
135
|
+
}
|
|
78
136
|
|
|
79
|
-
|
|
137
|
+
type Query = {
|
|
138
|
+
readonly query1: string;
|
|
139
|
+
readonly query2: number;
|
|
140
|
+
}
|
|
80
141
|
|
|
81
|
-
|
|
142
|
+
type Body = {
|
|
143
|
+
readonly body1: string;
|
|
144
|
+
readonly body2: number;
|
|
145
|
+
}
|
|
82
146
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
147
|
+
const url = '/entrypoint/:param1/:param2';
|
|
148
|
+
const method = Method.POST;
|
|
149
|
+
const options = {
|
|
150
|
+
// определяет заголовки, которые будут отправлены вместе с запросом
|
|
151
|
+
headers: {
|
|
152
|
+
'Some-Header': 'Some-Value',
|
|
153
|
+
},
|
|
154
|
+
// параметры для заполнения адреса запроса
|
|
155
|
+
params: {
|
|
156
|
+
param1: 'value-param',
|
|
157
|
+
param2: 1,
|
|
158
|
+
},
|
|
159
|
+
// параметры запроса
|
|
160
|
+
query: {
|
|
161
|
+
query1: 'value-query',
|
|
162
|
+
query2: 2,
|
|
163
|
+
},
|
|
164
|
+
// тело запроса
|
|
165
|
+
body: {
|
|
166
|
+
body1: 'value-body',
|
|
167
|
+
body2: 3,
|
|
168
|
+
},
|
|
169
|
+
// обязательное поле: определяет требуется ли передавать данные об авторизации
|
|
170
|
+
withAuthorization: true,
|
|
171
|
+
// определяет требуется ли отправлять заголовки в кроссдоменных запросах
|
|
172
|
+
withCredentials: false,
|
|
173
|
+
// определяет какой обработчик ответа будет использован (см. разедл обработка ответа от сервера)
|
|
174
|
+
responseMiddleware: defaultResponseMiddleware,
|
|
175
|
+
// определяет какой обработчик ошибок будет использован (см. разедл обработка ошибок)
|
|
176
|
+
errorMiddleware: defaultErrorMiddleware,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const { data1, data2 } = await httpClient.request<Method.POST, Result, Params, Query, Body>(url, method, options);
|
|
89
180
|
```
|
|
90
|
-
|
|
181
|
+
|
|
182
|
+
Он эквивалентен следующему запросу:
|
|
91
183
|
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
param1: 'XXXXX-XXXXX',
|
|
97
|
-
},
|
|
98
|
-
query: {
|
|
99
|
-
query1: 0,
|
|
100
|
-
},
|
|
101
|
-
body: {
|
|
102
|
-
body1: true,
|
|
103
|
-
},
|
|
104
|
-
withAuthorization: false,
|
|
105
|
-
withCredentials: false,
|
|
106
|
-
});
|
|
107
|
-
} catch (error: unknown) {
|
|
108
|
-
// TODO: error capture
|
|
109
|
-
}
|
|
184
|
+
curl -XPOST 'https://pushwoosh.com/entrypoint/value-param/1?query1=value-query&query2=2' -H "Some-Header: Some-Value" -H "Authorization: Bearer ..." -d '{
|
|
185
|
+
"body1": "value-body",
|
|
186
|
+
"body2": 3
|
|
187
|
+
}'
|
|
110
188
|
```
|
|
111
189
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
`params` - параметры, которые будут подставлены в шаблон `URL`
|
|
115
|
-
|
|
116
|
-
`query` - параметры, которые будут преобразованы к `Query String`
|
|
117
|
-
|
|
118
|
-
`body` - параметры, которые будут преобразованы к `Request Payload` (недоступен для `GET`)
|
|
119
|
-
|
|
120
|
-
`withAuthorization` - передавать заголовок `Authorization` в заголовке
|
|
121
|
-
|
|
122
|
-
`withCredentials` - передавать идентификационные данные в кросс доменных запросах
|
|
123
|
-
|
|
124
|
-
`response` - ответ от сервера:
|
|
190
|
+
Также существуют упрощенные варианты запросов:
|
|
125
191
|
|
|
192
|
+
##### GET
|
|
126
193
|
```typescript
|
|
127
|
-
|
|
194
|
+
const result = httpClient.get<Result, Params, Query>(url, options);
|
|
128
195
|
```
|
|
129
196
|
|
|
130
|
-
|
|
131
|
-
Все запросы имеют упрощенную структуру с указанием типа запроса:
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
curl 'https://app.pushwoosh.com/entrypoint' \
|
|
135
|
-
-H 'accept: application/json, text/plain, */*' \
|
|
136
|
-
-H 'authorization: Bearer SOME-ACCESS-TOKEN' \
|
|
137
|
-
```
|
|
138
|
-
Можно выполнить следующей конструкцией:
|
|
197
|
+
##### POST
|
|
139
198
|
```typescript
|
|
140
|
-
|
|
141
|
-
const result = httpClient.get<never, never, { some: string }>('/entrypoint', {
|
|
142
|
-
withAuthorization: true,
|
|
143
|
-
responseMiddleware: (response: Response<{some: string}>): { some: string } => {
|
|
144
|
-
// если стандартный middleware для разбора ответа от сервера не подходит
|
|
145
|
-
// например когда ответ от сервера всегда 200, а коды ошибок зашиты в самом ответе.
|
|
146
|
-
// {
|
|
147
|
-
// status: 200,
|
|
148
|
-
// message: 'Ok',
|
|
149
|
-
// result: {
|
|
150
|
-
// status: 200,
|
|
151
|
-
// message: 'Ok',
|
|
152
|
-
// response: {
|
|
153
|
-
// some: 'some-string',
|
|
154
|
-
// },
|
|
155
|
-
// },
|
|
156
|
-
// }
|
|
157
|
-
const jsonResponse = httpClient.baseResponseMiddleware(response);
|
|
158
|
-
if (jsonResponse.status !== 200) {
|
|
159
|
-
// map jsonResponse error to HttpClient error and throw it.
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return jsonResponse.result;
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
} catch (error: unknown) {
|
|
166
|
-
// TODO: error capture
|
|
167
|
-
}
|
|
199
|
+
const result = httpClient.post<Result, Params, Query, Body>(url, options);
|
|
168
200
|
```
|
|
169
|
-
В данном примере приходит сразу `result`, а если http запрос вернул `response.status` >= `400`, то произойдет выброс исключения.
|
|
170
|
-
|
|
171
|
-
## Обработка ошибок:
|
|
172
201
|
|
|
202
|
+
##### PUT
|
|
173
203
|
```typescript
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const result = httpClient.get<never, never, never>('/entrypoint', {
|
|
178
|
-
withAuthorization: true,
|
|
179
|
-
});
|
|
180
|
-
} catch (error: unknown) {
|
|
181
|
-
if (error instanceof UnauthorizedError) {
|
|
182
|
-
// TODO: Если не авторизован...
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (error instanceof ForbiddenError) {
|
|
186
|
-
// TODO: Если запрещено выполнять запрос...
|
|
187
|
-
}
|
|
204
|
+
const result = httpClient.put<Result, Params, Query, Body>(url, options);
|
|
205
|
+
```
|
|
188
206
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
##### PATCH
|
|
208
|
+
```typescript
|
|
209
|
+
const result = httpClient.patch<Result, Params, Query, Body>(url, options);
|
|
210
|
+
```
|
|
192
211
|
|
|
193
|
-
|
|
194
|
-
|
|
212
|
+
##### DELETE
|
|
213
|
+
```typescript
|
|
214
|
+
const result = httpClient.delete<Result, Params, Query, Body>(url, options);
|
|
195
215
|
```
|
|
196
216
|
|
|
197
|
-
###
|
|
198
|
-
|
|
217
|
+
### Обработка ответов от сервера
|
|
218
|
+
Так как ответ от сервера не стандартизирован, то при обращении к разным эндпоинтам может потребоваться своя валидация и парсинг.
|
|
199
219
|
<br/>
|
|
200
|
-
|
|
220
|
+
Для этого в каждом запросе можно указать свой обработчик запроса.
|
|
201
221
|
<br/>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
Каждая ошибка имеет свой класс:
|
|
205
|
-
|
|
206
|
-
#### Ошибки связанные с самим модулем:
|
|
207
|
-
`InternalError` - Класс описывающий внутреннюю ошибку HttpClient
|
|
208
|
-
|
|
209
|
-
#### Ошибки связанные с ответами от сервера:
|
|
210
|
-
`BadRequestError` - Класс описывающий ошибку "Плохой запрос"
|
|
211
|
-
|
|
212
|
-
`UnauthorizedError` - Класс описывающий ошибку "Не авторизовано"
|
|
213
|
-
|
|
214
|
-
`ForbiddenError` - Класс описывающий ошибку "Запрещено"
|
|
215
|
-
|
|
216
|
-
`NotFoundError` - Класс описывающий ошибку "Не найден"
|
|
217
|
-
|
|
218
|
-
`MethodNotAllowedError` - Класс описывающий ошибку "Метод не доступен"
|
|
219
|
-
|
|
220
|
-
`UnsupportedMediaTypeError` - Класс описывающий ошибку "Медиа формат не поддерживается сервером"
|
|
222
|
+
Например, если ответ от сервера всегда 200, а внутри него лежит JSON с данными, то можно поступить следующим образом:
|
|
221
223
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
```typescript
|
|
225
|
+
type Result = {
|
|
226
|
+
readonly data1: string;
|
|
227
|
+
readonly data2: number;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type JSONResponse = {
|
|
231
|
+
readonly result: Result;
|
|
232
|
+
readonly code: number;
|
|
233
|
+
readonly message: string;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const { data1, data2 } = await httpClient.post<Result, never, never, never>('/entrypoint', {
|
|
237
|
+
withAuthorization: true,
|
|
238
|
+
responseMiddleware: (response) => {
|
|
239
|
+
const jsonResponse = defaultResponseMiddleware<JSONResponse>(response);
|
|
240
|
+
if (jsonResponse.code !== 200) {
|
|
241
|
+
switch (jsonResponse.code) {
|
|
242
|
+
case 210:
|
|
243
|
+
case 401:
|
|
244
|
+
throw new UnauthorizedError(jsonResponse.message, response.details);
|
|
245
|
+
default:
|
|
246
|
+
throw new UntiledError(jsonResponse.message, jsonResponse.code, response.details);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
226
249
|
|
|
227
|
-
|
|
250
|
+
return jsonResponse.result;
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
228
254
|
|
|
229
|
-
|
|
255
|
+
При обработке ответа от сервера очень важно корректно указывать ошибки, так как:
|
|
256
|
+
1. Если при обработке ответа от сервера будет кинуто исключение UnauthorizedError, то HttpClient попробует обновить access token м выполнить запрос повторно
|
|
257
|
+
2. Настраивается глобальный обработчик ошибок, который при возникновении тех или иных ситуаций их обрабатывает
|
|
230
258
|
|
|
231
|
-
|
|
259
|
+
### Обработка ошибок
|
|
260
|
+
Если в момент выполнения запроса происходит ошибка, то в первую очередь ее пробует обработать basicErrorMiddleware (если для конкретного запроса он не заменен на свой обработчик ошибок)
|
|
261
|
+
```typescript
|
|
262
|
+
import { ForbiddenError, HttpClientError, UnauthorizedError } from './http-client.errors';
|
|
263
|
+
|
|
264
|
+
const httpClient = new HttpClient(history, {
|
|
265
|
+
basicErrorMiddleware: async (error: unknown): Promise<never> => {
|
|
266
|
+
// если клиент не авторизован и пытается выполнить запрос к приватным данным, то пробуем его авторизовать
|
|
267
|
+
if (error instanceof UnauthorizedError) {
|
|
268
|
+
await httpClient.logout();
|
|
269
|
+
await httpClient.login();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
throw error;
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
```
|
|
232
276
|
|
|
233
|
-
|
|
234
|
-
|
|
277
|
+
Если ошибка попадает под список обрабатываемых, то Http Client ее обработает сам, если нет, она продолжит всплытие и ее можно будет обработать на месте.
|
|
278
|
+
|
|
279
|
+
#### Ошибки:
|
|
280
|
+
|
|
281
|
+
Все ошибки наследуются от родительского класса HttpClientError. Существуют следующие ошибки:
|
|
282
|
+
|
|
283
|
+
1. HttpClientError:
|
|
284
|
+
1. LogicError - ошибка в логике самого Http Client
|
|
285
|
+
2. ClassifiedError - категоризированная ошибка, никогда не используется как отдельный экземпляр, от нее наследуются все остальные ошибки.
|
|
286
|
+
1. UntiledError - ошибка не попавшая ни под одну из категорий ниже
|
|
287
|
+
2. BadRequestError - некорректный запрос (сервер не может обработать запрос из-за некорректного синтаксиса)
|
|
288
|
+
3. UnauthorizedError - не авторизован / некорректная авторизация
|
|
289
|
+
4. ForbiddenError - запрещено для выполнения (permissions)
|
|
290
|
+
5. NotFoundError - не найдено
|
|
291
|
+
6. MethodNotAllowedError - не доступно для выполнения (restrictions)
|
|
292
|
+
7. PayloadTooLargeError - слишком большой запрос
|
|
293
|
+
8. UnsupportedMediaTypeError - неподдерживаемое тип медиа
|
|
294
|
+
9. UnprocessableEntityError - сервер понял запрос, но не смог его обработать из-за логических ошибок
|
|
295
|
+
10. TooManyRequestsError - слишком много запросов
|
|
296
|
+
11. InternalServerError
|
|
297
|
+
12. NotImplementedError
|
|
298
|
+
13. BadGatewayError
|
|
299
|
+
14. ServiceUnavailableError
|
|
300
|
+
15. GatewayTimeoutError
|
|
301
|
+
|
|
302
|
+
Нужно понимать, что API поддерживают не все из приведенных выше ошибок или наоборот ошибок может не хватать
|
|
303
|
+
<br/>
|
|
304
|
+
Тут нужно придерживаться именно логики вызова этих ошибок и приводить разношерстный API к единому виду обработки
|
package/babel.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module.exports = (api) => {
|
|
2
|
+
api.cache.using(() => process.env.NODE_ENV);
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
presets: [
|
|
6
|
+
[
|
|
7
|
+
'@babel/preset-env',
|
|
8
|
+
{
|
|
9
|
+
modules: false,
|
|
10
|
+
corejs: 3,
|
|
11
|
+
useBuiltIns: 'entry',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
],
|
|
15
|
+
plugins: [
|
|
16
|
+
[
|
|
17
|
+
'@babel/plugin-transform-runtime',
|
|
18
|
+
{
|
|
19
|
+
corejs: 3,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
};
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,74 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushwoosh/http-client",
|
|
3
|
-
"version": "1.4.2",
|
|
4
|
-
"license": "MIT",
|
|
5
3
|
"main": "./dist/index.js",
|
|
6
4
|
"types": "./dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist"
|
|
9
|
-
],
|
|
10
5
|
"scripts": {
|
|
11
6
|
"start": "npm run development",
|
|
12
7
|
"build": "npm run production",
|
|
13
8
|
"development": "cross-env NODE_ENV=development webpack-dev-server --config webpack.development.config.js",
|
|
14
9
|
"production": "cross-env NODE_ENV=production webpack --config webpack.production.config.js",
|
|
15
|
-
"check": "npm run check:lint && npm run check:
|
|
16
|
-
"check:lint": "echo \"Run check:lint\" && eslint
|
|
10
|
+
"check": "npm run check:lint && npm run check:tests && npm run check:types",
|
|
11
|
+
"check:lint": "echo \"Run check:lint\" && eslint",
|
|
17
12
|
"check:tests": "echo \"Run check:tests\" && jest --passWithNoTests",
|
|
18
13
|
"check:types": "echo \"Run check:types\" && tsc --project tsconfig.json --noEmit"
|
|
19
14
|
},
|
|
20
15
|
"pre-commit": "check",
|
|
21
16
|
"dependencies": {
|
|
22
|
-
"@babel/runtime-corejs3": "^7.
|
|
23
|
-
"
|
|
17
|
+
"@babel/runtime-corejs3": "^7.17.2",
|
|
18
|
+
"axios": "^0.25.0",
|
|
19
|
+
"core-js": "^3.21.0",
|
|
24
20
|
"js-cookie": "^3.0.1",
|
|
25
21
|
"jwt-decode": "^3.1.2",
|
|
26
22
|
"path-to-regexp": "^6.2.0",
|
|
27
23
|
"qs": "^6.10.3"
|
|
28
24
|
},
|
|
29
25
|
"devDependencies": {
|
|
30
|
-
"@babel/core": "^7.
|
|
31
|
-
"@babel/plugin-transform-runtime": "^7.
|
|
32
|
-
"@babel/preset-env": "^7.16.
|
|
33
|
-
"@types/jest": "^
|
|
26
|
+
"@babel/core": "^7.17.2",
|
|
27
|
+
"@babel/plugin-transform-runtime": "^7.17.0",
|
|
28
|
+
"@babel/preset-env": "^7.16.11",
|
|
29
|
+
"@types/jest": "^27.4.0",
|
|
34
30
|
"@types/webpack-env": "^1.16.3",
|
|
35
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
36
|
-
"@typescript-eslint/parser": "^
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
|
32
|
+
"@typescript-eslint/parser": "^5.11.0",
|
|
37
33
|
"babel-loader": "^8.2.3",
|
|
38
|
-
"clean-webpack-plugin": "^
|
|
34
|
+
"clean-webpack-plugin": "^4.0.0",
|
|
39
35
|
"cross-env": "^7.0.3",
|
|
40
|
-
"dotenv": "^
|
|
41
|
-
"eslint": "^
|
|
42
|
-
"eslint-config-airbnb": "^
|
|
43
|
-
"eslint-config-airbnb-typescript": "^
|
|
44
|
-
"eslint-
|
|
45
|
-
"eslint-plugin-
|
|
46
|
-
"eslint-plugin-import": "^2.25.3",
|
|
36
|
+
"dotenv": "^16.0.0",
|
|
37
|
+
"eslint": "^8.8.0",
|
|
38
|
+
"eslint-config-airbnb": "^19.0.4",
|
|
39
|
+
"eslint-config-airbnb-typescript": "^16.1.0",
|
|
40
|
+
"eslint-plugin-compat": "^4.0.2",
|
|
41
|
+
"eslint-plugin-import": "^2.25.4",
|
|
47
42
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
|
48
|
-
"eslint-plugin-react": "^7.
|
|
43
|
+
"eslint-plugin-react": "^7.28.0",
|
|
49
44
|
"eslint-plugin-react-hooks": "^4.3.0",
|
|
50
|
-
"jest": "^
|
|
45
|
+
"jest": "^27.5.1",
|
|
51
46
|
"pre-commit": "^1.2.2",
|
|
52
|
-
"terser-webpack-plugin": "^
|
|
53
|
-
"ts-jest": "^
|
|
54
|
-
"ts-loader": "^
|
|
55
|
-
"typescript": "^4.5.
|
|
56
|
-
"
|
|
57
|
-
"webpack": "^4.
|
|
58
|
-
"webpack-
|
|
59
|
-
"webpack-dev-server": "^3.11.3",
|
|
47
|
+
"terser-webpack-plugin": "^5.3.1",
|
|
48
|
+
"ts-jest": "^27.1.3",
|
|
49
|
+
"ts-loader": "^9.2.6",
|
|
50
|
+
"typescript": "^4.5.5",
|
|
51
|
+
"webpack": "^5.68.0",
|
|
52
|
+
"webpack-cli": "^4.9.2",
|
|
53
|
+
"webpack-dev-server": "^4.7.4",
|
|
60
54
|
"webpack-merge": "^5.8.0"
|
|
61
55
|
},
|
|
62
56
|
"peerDependencies": {
|
|
63
|
-
"@types/history": "^4.7.
|
|
57
|
+
"@types/history": "^4.7.11",
|
|
64
58
|
"@types/js-cookie": "^3.0.1",
|
|
65
59
|
"@types/qs": "^6.9.7",
|
|
66
|
-
"axios": "^0.24.0",
|
|
67
60
|
"history": "^4.10.1",
|
|
68
61
|
"tslib": "^2.3.1"
|
|
69
62
|
},
|
|
70
63
|
"engines": {
|
|
71
|
-
"node": ">=
|
|
72
|
-
"npm": ">=
|
|
73
|
-
}
|
|
64
|
+
"node": ">=16.0.0",
|
|
65
|
+
"npm": ">=7.0.0"
|
|
66
|
+
},
|
|
67
|
+
"version": "1.5.0"
|
|
74
68
|
}
|