bx-rest 1.1.0 → 1.3.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/CHANGELOG.md +34 -0
- package/README.en.md +54 -22
- package/README.md +54 -22
- package/esm2022/functions/errors.mjs +64 -8
- package/esm2022/rest/base.mjs +3 -3
- package/esm2022/services/http/HttpBX.mjs +26 -12
- package/esm2022/services/http/sessionKey.mjs +8 -1
- package/esm2022/services/navvy/extends/navvy-pag-base.mjs +26 -4
- package/esm2022/services/navvy/navvy-alternative-pagination-navigation.mjs +2 -2
- package/esm2022/services/navvy/navvy-pag-nav-result-key-object.mjs +2 -2
- package/esm2022/services/navvy/navvy-pag-nav-result-key.mjs +2 -2
- package/esm2022/services/navvy/navvy-pag-nav-tasks.mjs +2 -2
- package/esm2022/services/navvy/navvy-pag-nav-with-useless-key.mjs +2 -2
- package/esm2022/services/navvy/navvy-pag.mjs +2 -2
- package/esm2022/typification/settings.mjs +1 -1
- package/fesm2022/bx-rest.mjs +126 -28
- package/fesm2022/bx-rest.mjs.map +1 -1
- package/functions/errors.d.ts +46 -6
- package/package.json +1 -1
- package/rest/base.d.ts +1 -1
- package/services/http/HttpBX.d.ts +15 -4
- package/services/http/sessionKey.d.ts +5 -0
- package/services/navvy/extends/navvy-pag-base.d.ts +18 -4
- package/typification/settings.d.ts +7 -0
package/fesm2022/bx-rest.mjs
CHANGED
|
@@ -587,6 +587,13 @@ class SessionKeyServices {
|
|
|
587
587
|
}
|
|
588
588
|
return this.getKeyAuth() === 'OAuth2';
|
|
589
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Статичные заголовки из `BXRestSettings.headers` — копия, чтобы правка
|
|
592
|
+
* результата не меняла сами настройки.
|
|
593
|
+
*/
|
|
594
|
+
getHeaders() {
|
|
595
|
+
return { ...this.BX_REST_SETTINGS.headers };
|
|
596
|
+
}
|
|
590
597
|
getSessid() {
|
|
591
598
|
const check = this.getSearchParam('sessid');
|
|
592
599
|
if (check) {
|
|
@@ -970,21 +977,26 @@ class HttpBXServices extends HttpServices {
|
|
|
970
977
|
*
|
|
971
978
|
* @param name
|
|
972
979
|
* @param params
|
|
980
|
+
* @param restApi
|
|
981
|
+
* @param headers заголовки только для этого запроса, поверх `BXRestSettings.headers`
|
|
973
982
|
*/
|
|
974
|
-
post(name, params = {}, restApi = false) {
|
|
983
|
+
post(name, params = {}, restApi = false, headers) {
|
|
975
984
|
const method = this.getNameMethod(name);
|
|
976
|
-
return this.httpPost(restApi ? `api/${method}` : method, params);
|
|
985
|
+
return this.httpPost(restApi ? `api/${method}` : method, params, undefined, headers);
|
|
977
986
|
}
|
|
978
987
|
/**
|
|
979
988
|
* Второстепенный метод для запросов. По возможности лучше не использовать
|
|
980
989
|
*
|
|
981
990
|
* @param name
|
|
982
991
|
* @param params
|
|
992
|
+
* @param headers заголовки только для этого запроса, поверх `BXRestSettings.headers`
|
|
983
993
|
*/
|
|
984
|
-
get(name, params = {}) {
|
|
985
|
-
return this.httpGet(this.getNameMethod(name), params);
|
|
994
|
+
get(name, params = {}, headers) {
|
|
995
|
+
return this.httpGet(this.getNameMethod(name), params, undefined, headers);
|
|
986
996
|
}
|
|
987
|
-
|
|
997
|
+
// settings — только для совместимости сигнатуры с HttpServices: здесь тело
|
|
998
|
+
// уходит JSON-ом/urlencoded, и пересчёт дат из iHttpParamSettings не нужен.
|
|
999
|
+
httpPost(url, params = {}, settings, headers) {
|
|
988
1000
|
// defer() откладывает всё содержимое (в том числе чтение BXRestSettings
|
|
989
1001
|
// через this.session.*) до реальной подписки — иначе даже с "живым"
|
|
990
1002
|
// геттером в SessionKeyServices настройки читались бы в момент вызова
|
|
@@ -1012,19 +1024,17 @@ class HttpBXServices extends HttpServices {
|
|
|
1012
1024
|
// Преобразуем параметры в формат x-www-form-urlencoded
|
|
1013
1025
|
const bodyString = this.serializeBitrixParams(paramsClone);
|
|
1014
1026
|
return from(this.axiosInstance.post(fullUrl, bodyString, {
|
|
1015
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1027
|
+
headers: this.buildHeaders(headers, { 'Content-Type': 'application/x-www-form-urlencoded' }),
|
|
1016
1028
|
}).then((response) => response.data));
|
|
1017
1029
|
}
|
|
1018
1030
|
return from(this.axiosInstance.post(fullUrl, paramsClone, {
|
|
1019
1031
|
withCredentials: this.session.getWithCredentials(),
|
|
1020
|
-
headers: {
|
|
1021
|
-
'Content-Type': 'application/json',
|
|
1022
|
-
}
|
|
1032
|
+
headers: this.buildHeaders(headers, { 'Content-Type': 'application/json' }),
|
|
1023
1033
|
}).then((response) => response.data));
|
|
1024
1034
|
}));
|
|
1025
1035
|
});
|
|
1026
1036
|
}
|
|
1027
|
-
httpGet(url, params = {}) {
|
|
1037
|
+
httpGet(url, params = {}, settings, headers) {
|
|
1028
1038
|
// Та же причина, что и у httpPost выше: откладываем чтение BXRestSettings
|
|
1029
1039
|
// до подписки, чтобы `get()` вёл себя так же "лениво", как и `post()`
|
|
1030
1040
|
// (симметрия, которой не было до этого — GET резолвил настройки в момент
|
|
@@ -1046,10 +1056,21 @@ class HttpBXServices extends HttpServices {
|
|
|
1046
1056
|
const fullUrl = prepareBaseAddress(baseUrl) + url;
|
|
1047
1057
|
const queryString = new URLSearchParams(paramsClone).toString();
|
|
1048
1058
|
const finalUrl = `${fullUrl}?${queryString}`;
|
|
1049
|
-
return from(this.axiosInstance.get(finalUrl
|
|
1059
|
+
return from(this.axiosInstance.get(finalUrl, {
|
|
1060
|
+
headers: this.buildHeaders(headers),
|
|
1061
|
+
}).then((response) => response.data));
|
|
1050
1062
|
}));
|
|
1051
1063
|
});
|
|
1052
1064
|
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Заголовки запроса: `BXRestSettings.headers`, поверх них — заголовки
|
|
1067
|
+
* конкретного запроса, поверх всего — `required`. В `required` идёт
|
|
1068
|
+
* `Content-Type`: от него зависит, как сервер разберёт тело, поэтому
|
|
1069
|
+
* пользовательские заголовки его не перебивают.
|
|
1070
|
+
*/
|
|
1071
|
+
buildHeaders(headers, required = {}) {
|
|
1072
|
+
return { ...this.session.getHeaders(), ...headers, ...required };
|
|
1073
|
+
}
|
|
1053
1074
|
serializeBitrixParams(params) {
|
|
1054
1075
|
return serializeBitrixParams(params);
|
|
1055
1076
|
}
|
|
@@ -1059,20 +1080,75 @@ class HttpBXServices extends HttpServices {
|
|
|
1059
1080
|
}
|
|
1060
1081
|
|
|
1061
1082
|
/**
|
|
1062
|
-
*
|
|
1063
|
-
* Bitrix REST call resolves as a Bitrix-level API error, e.g.
|
|
1064
|
-
* `INSUFFICIENT_SCOPE` or `expired_token`.
|
|
1083
|
+
* A Bitrix-level API error, e.g. `INSUFFICIENT_SCOPE` or `expired_token`.
|
|
1065
1084
|
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1085
|
+
* `.res()` fails with it whenever the portal answers with a Bitrix error and
|
|
1086
|
+
* a non-2xx HTTP status — which is how Bitrix sends most of its errors. When
|
|
1087
|
+
* the error comes in the body of a 2xx response, `.res()` resolves as
|
|
1088
|
+
* `undefined` by default and fails with it only with
|
|
1089
|
+
* `{ throwOnApiError: true }` — see the "Error handling" section of the README.
|
|
1068
1090
|
*/
|
|
1069
1091
|
class BXRestApiError extends Error {
|
|
1070
|
-
constructor(error, error_description) {
|
|
1071
|
-
super(error_description || error || 'Bitrix API error');
|
|
1092
|
+
constructor(error, error_description, details) {
|
|
1093
|
+
super(error_description || error || 'Bitrix API error', details?.cause !== undefined ? { cause: details.cause } : undefined);
|
|
1072
1094
|
this.error = error;
|
|
1073
1095
|
this.error_description = error_description;
|
|
1074
1096
|
this.name = 'BXRestApiError';
|
|
1097
|
+
this.status = details?.status;
|
|
1098
|
+
this.method = details?.method;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Recognises a Bitrix error body: REST v2 `{ error, error_description }` and
|
|
1103
|
+
* REST v3 `{ error: { code, message } }`. Returns `undefined` for anything
|
|
1104
|
+
* else — a success payload, an HTML page from a proxy, an empty body.
|
|
1105
|
+
*
|
|
1106
|
+
* An empty `error` still counts: Bitrix answers e.g. `crm.deal.get` for a
|
|
1107
|
+
* missing ID with `{ error: '', error_description: 'Not found' }`.
|
|
1108
|
+
*/
|
|
1109
|
+
function parseBXRestErrorBody(body) {
|
|
1110
|
+
if (!body || typeof body !== 'object' || !('error' in body)) {
|
|
1111
|
+
return undefined;
|
|
1112
|
+
}
|
|
1113
|
+
const { error, error_description } = body;
|
|
1114
|
+
if (typeof error === 'string') {
|
|
1115
|
+
return {
|
|
1116
|
+
error,
|
|
1117
|
+
error_description: typeof error_description === 'string' ? error_description : undefined
|
|
1118
|
+
};
|
|
1075
1119
|
}
|
|
1120
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
1121
|
+
const { code, message } = error;
|
|
1122
|
+
return {
|
|
1123
|
+
error: String(code),
|
|
1124
|
+
error_description: typeof message === 'string' ? message : undefined
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
return undefined;
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Turns an HTTP failure into a `BXRestApiError` when the portal answered with
|
|
1131
|
+
* a Bitrix error body. Bitrix sends most errors with a non-2xx status
|
|
1132
|
+
* (401 `expired_token`, 404 `ERROR_METHOD_NOT_FOUND`, 503
|
|
1133
|
+
* `QUERY_LIMIT_EXCEEDED`, 400 "Not found" from `crm.*.get`, ...), and axios
|
|
1134
|
+
* rejects on those.
|
|
1135
|
+
*
|
|
1136
|
+
* Returns `undefined` for network errors and for responses that are not a
|
|
1137
|
+
* Bitrix error body, so the caller can keep its own handling for them.
|
|
1138
|
+
*/
|
|
1139
|
+
function bxRestApiErrorFromHttp(err, method) {
|
|
1140
|
+
if (!axios.isAxiosError(err) || !err.response) {
|
|
1141
|
+
return undefined;
|
|
1142
|
+
}
|
|
1143
|
+
const parsed = parseBXRestErrorBody(err.response.data);
|
|
1144
|
+
if (!parsed) {
|
|
1145
|
+
return undefined;
|
|
1146
|
+
}
|
|
1147
|
+
return new BXRestApiError(parsed.error, parsed.error_description, {
|
|
1148
|
+
status: err.response.status,
|
|
1149
|
+
method,
|
|
1150
|
+
cause: err
|
|
1151
|
+
});
|
|
1076
1152
|
}
|
|
1077
1153
|
|
|
1078
1154
|
class NavvyPagBase {
|
|
@@ -1084,8 +1160,21 @@ class NavvyPagBase {
|
|
|
1084
1160
|
this.pageSize = 50; // стандартный размер страницы
|
|
1085
1161
|
this.http = new HttpBXServices();
|
|
1086
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* HTTP-заголовки только для этого запроса — поверх `BXRestSettings.headers`
|
|
1165
|
+
* (при совпадении имени побеждает значение отсюда). Повторные вызовы
|
|
1166
|
+
* дополняют уже заданные заголовки. Действует на `.res()`, `.resVanilla()`,
|
|
1167
|
+
* `.mapForVanilla()` и на все batch-запросы `.resAll()`.
|
|
1168
|
+
*
|
|
1169
|
+
* @example
|
|
1170
|
+
* bx.crm.deal.list({ ... }).withHeaders({ 'X-Request-Source': 'report' }).resAll()
|
|
1171
|
+
*/
|
|
1172
|
+
withHeaders(headers) {
|
|
1173
|
+
this.requestHeaders = { ...this.requestHeaders, ...headers };
|
|
1174
|
+
return this;
|
|
1175
|
+
}
|
|
1087
1176
|
resVanilla() {
|
|
1088
|
-
return this.http.post(this.url, this.param, this.restApi);
|
|
1177
|
+
return this.http.post(this.url, this.param, this.restApi, this.requestHeaders);
|
|
1089
1178
|
}
|
|
1090
1179
|
mapForVanilla() {
|
|
1091
1180
|
return this.resVanilla()
|
|
@@ -1104,9 +1193,11 @@ class NavvyPagBase {
|
|
|
1104
1193
|
: v));
|
|
1105
1194
|
}
|
|
1106
1195
|
mapAndError(request, options) {
|
|
1196
|
+
const method = this.http.getNameMethod(this.url);
|
|
1107
1197
|
return request.pipe(map(v => {
|
|
1108
1198
|
if (options?.throwOnApiError && v && !instanceOfiBXRestAnswerSuccess(v)) {
|
|
1109
|
-
|
|
1199
|
+
const parsed = parseBXRestErrorBody(v);
|
|
1200
|
+
throw new BXRestApiError(parsed ? parsed.error : v.error, parsed ? parsed.error_description : v.error_description, { method });
|
|
1110
1201
|
}
|
|
1111
1202
|
return BXRestMapResult(v);
|
|
1112
1203
|
}), catchError((err) => {
|
|
@@ -1114,6 +1205,13 @@ class NavvyPagBase {
|
|
|
1114
1205
|
if (err instanceof BXRestApiError) {
|
|
1115
1206
|
throw err;
|
|
1116
1207
|
}
|
|
1208
|
+
// Большинство ошибок Bitrix приходит со статусом не 2xx, и axios
|
|
1209
|
+
// отклоняет такой ответ — достаём из него код и описание ошибки,
|
|
1210
|
+
// а не отдаём безликое "AxiosError: Request failed with status code 400".
|
|
1211
|
+
const apiError = bxRestApiErrorFromHttp(err, method);
|
|
1212
|
+
if (apiError) {
|
|
1213
|
+
throw apiError;
|
|
1214
|
+
}
|
|
1117
1215
|
throw new Error((err.error) ? err.error : err);
|
|
1118
1216
|
}));
|
|
1119
1217
|
}
|
|
@@ -3042,8 +3140,8 @@ class BXRest {
|
|
|
3042
3140
|
profile() {
|
|
3043
3141
|
return this.http.post(['profile']);
|
|
3044
3142
|
}
|
|
3045
|
-
batch(param) {
|
|
3046
|
-
return this.http.post(['batch'], param);
|
|
3143
|
+
batch(param, headers) {
|
|
3144
|
+
return this.http.post(['batch'], param, false, headers);
|
|
3047
3145
|
}
|
|
3048
3146
|
}
|
|
3049
3147
|
|
|
@@ -3095,7 +3193,7 @@ class NavvyAlterPagNav extends NavvyPagBase {
|
|
|
3095
3193
|
return this.http.getNameMethod(this.url)
|
|
3096
3194
|
+ '?' + serializeBitrixParams(param);
|
|
3097
3195
|
})
|
|
3098
|
-
});
|
|
3196
|
+
}, this.requestHeaders);
|
|
3099
3197
|
});
|
|
3100
3198
|
return from(batchRequests).pipe(concatMap(request => request), // Выполняет запросы по очереди
|
|
3101
3199
|
toArray(), // Собирает все результаты в массив
|
|
@@ -3151,7 +3249,7 @@ class NavvyPagNavWithUselessKey extends NavvyPagBase {
|
|
|
3151
3249
|
return this.http.getNameMethod(this.url)
|
|
3152
3250
|
+ '?' + serializeBitrixParams(param);
|
|
3153
3251
|
})
|
|
3154
|
-
}).pipe(map(v => {
|
|
3252
|
+
}, this.requestHeaders).pipe(map(v => {
|
|
3155
3253
|
if (v && instanceOfiBXRestAnswerSuccess(v) && v.result && v.result.result && items.result) {
|
|
3156
3254
|
return Object.assign({}, items.result, v.result.result);
|
|
3157
3255
|
}
|
|
@@ -3237,7 +3335,7 @@ class NavvyPagNavTasks extends NavvyPagBase {
|
|
|
3237
3335
|
return this.http.getNameMethod(this.url)
|
|
3238
3336
|
+ '?' + serializeBitrixParams(param);
|
|
3239
3337
|
})
|
|
3240
|
-
});
|
|
3338
|
+
}, this.requestHeaders);
|
|
3241
3339
|
});
|
|
3242
3340
|
return from(batchRequests).pipe(concatMap(request => request), // Выполняет запросы по очереди
|
|
3243
3341
|
toArray(), // Собирает все результаты в массив
|
|
@@ -3301,7 +3399,7 @@ class NavvyPag extends NavvyPagBase {
|
|
|
3301
3399
|
return this.http.getNameMethod(this.url)
|
|
3302
3400
|
+ '?' + serializeBitrixParams(param);
|
|
3303
3401
|
})
|
|
3304
|
-
}).pipe();
|
|
3402
|
+
}, this.requestHeaders).pipe();
|
|
3305
3403
|
});
|
|
3306
3404
|
return from(batchRequests).pipe(concatMap(request => request), // Выполняет запросы по очереди
|
|
3307
3405
|
toArray(), // Собирает все результаты в массив
|
|
@@ -3371,7 +3469,7 @@ class NavvyPagNavResultKey extends NavvyPagBase {
|
|
|
3371
3469
|
return this.http.getNameMethod(this.url)
|
|
3372
3470
|
+ '?' + serializeBitrixParams(param);
|
|
3373
3471
|
})
|
|
3374
|
-
});
|
|
3472
|
+
}, this.requestHeaders);
|
|
3375
3473
|
});
|
|
3376
3474
|
return from(batchRequests).pipe(concatMap(request => request), toArray(), map(results => {
|
|
3377
3475
|
const firstPage = items.result?.[this.key];
|
|
@@ -3427,7 +3525,7 @@ class NavvyPagNavResultKeyObject extends NavvyPagBase {
|
|
|
3427
3525
|
return bxRest.batch({
|
|
3428
3526
|
halt: 1,
|
|
3429
3527
|
cmd: [this.http.getNameMethod(this.url) + '?' + serializeBitrixParams(param)]
|
|
3430
|
-
});
|
|
3528
|
+
}, this.requestHeaders);
|
|
3431
3529
|
});
|
|
3432
3530
|
return from(commands).pipe(concatMap(request => request), toArray(), mergeMap(results => {
|
|
3433
3531
|
const merged = { ...(items.result?.[this.key] ?? {}) };
|