sfmc-sdk 0.6.3 → 0.8.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/.eslintrc.json +38 -0
- package/.fork/.prettierrc +6 -0
- package/.fork/custom-commands.json +26 -0
- package/.gitattributes +1 -4
- package/.husky/commit-msg +10 -0
- package/.husky/post-checkout +5 -0
- package/lib/auth.js +18 -19
- package/lib/index.js +1 -0
- package/lib/rest.js +71 -34
- package/lib/soap.js +144 -102
- package/lib/util.js +16 -5
- package/package.json +12 -8
- package/test/auth.test.js +16 -16
- package/test/resources/soap.json +50 -0
- package/test/rest.test.js +15 -14
- package/test/soap.test.js +76 -26
package/.eslintrc.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"mocha": true
|
|
6
6
|
},
|
|
7
7
|
"extends": [
|
|
8
|
+
"plugin:unicorn/recommended",
|
|
8
9
|
"eslint:recommended",
|
|
9
10
|
"plugin:mocha/recommended",
|
|
10
11
|
"plugin:jsdoc/recommended",
|
|
@@ -18,5 +19,42 @@
|
|
|
18
19
|
"parserOptions": {
|
|
19
20
|
"ecmaVersion": 2020,
|
|
20
21
|
"sourceType": "module"
|
|
22
|
+
},
|
|
23
|
+
"settings": {
|
|
24
|
+
"jsdoc": {
|
|
25
|
+
"mode": "jsdoc",
|
|
26
|
+
"preferredTypes": {
|
|
27
|
+
"array": "Array",
|
|
28
|
+
"array.<>": "[]",
|
|
29
|
+
"Array.<>": "[]",
|
|
30
|
+
"array<>": "[]",
|
|
31
|
+
"Array<>": "[]",
|
|
32
|
+
"Object": "object",
|
|
33
|
+
"object.<>": "Object.<>",
|
|
34
|
+
"object<>": "Object.<>",
|
|
35
|
+
"Object<>": "Object.<>",
|
|
36
|
+
"promise": "Promise",
|
|
37
|
+
"promise.<>": "Promise.<>",
|
|
38
|
+
"promise<>": "Promise.<>",
|
|
39
|
+
"Promise<>": "Promise.<>"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"rules": {
|
|
44
|
+
"unicorn/prefer-module": "off",
|
|
45
|
+
"unicorn/numeric-separators-style": "off",
|
|
46
|
+
"jsdoc/check-line-alignment": 2,
|
|
47
|
+
"jsdoc/require-jsdoc": [
|
|
48
|
+
"warn",
|
|
49
|
+
{
|
|
50
|
+
"require": {
|
|
51
|
+
"FunctionDeclaration": true,
|
|
52
|
+
"MethodDefinition": true,
|
|
53
|
+
"ClassDeclaration": true,
|
|
54
|
+
"ArrowFunctionExpression": false,
|
|
55
|
+
"FunctionExpression": true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
21
59
|
}
|
|
22
60
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "Create PR to develop branch",
|
|
4
|
+
"target": "ref",
|
|
5
|
+
"refTargets": [
|
|
6
|
+
"localbranch",
|
|
7
|
+
"remotebranch"
|
|
8
|
+
],
|
|
9
|
+
"action": {
|
|
10
|
+
"type": "url",
|
|
11
|
+
"url": "https://github.com/DougMidgley/SFMC-SDK/compare/develop...$shortname?expand=1"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "Create PR to hotfix branch",
|
|
16
|
+
"target": "ref",
|
|
17
|
+
"refTargets": [
|
|
18
|
+
"localbranch",
|
|
19
|
+
"remotebranch"
|
|
20
|
+
],
|
|
21
|
+
"action": {
|
|
22
|
+
"type": "url",
|
|
23
|
+
"url": "https://github.com/DougMidgley/SFMC-SDK/compare/hotfix...$shortname?expand=1"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]
|
package/.gitattributes
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
. "$(dirname "$0")/_/husky.sh"
|
|
3
|
+
INPUT_FILE=$1
|
|
4
|
+
START_LINE=`head -n1 $INPUT_FILE`
|
|
5
|
+
PATTERN="^(#[[:digit:]]|Merge)"
|
|
6
|
+
|
|
7
|
+
if ! [[ "$START_LINE" =~ $PATTERN ]] ; then
|
|
8
|
+
echo "Bad commit message, see example: \"#431 commit message\", you provided: \"$START_LINE\""
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
git config commit.template .git/templatemessage
|
|
3
|
+
TICKETID=`git rev-parse --abbrev-ref HEAD | LC_ALL=en_US.utf8 grep -oP '((feature|bug|bugfix|fix|hotfix|task|chore)\/)\K\d{1,7}'`
|
|
4
|
+
echo "[POST_CHECKOUT] Setting template commit to $TICKETID"
|
|
5
|
+
echo "#$TICKETID: " > ".git/templatemessage"
|
package/lib/auth.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
2
|
const axios = require('axios');
|
|
4
3
|
const { isConnectionError, RestError } = require('./util');
|
|
5
4
|
const AVAIALABLE_SCOPES = [
|
|
@@ -99,7 +98,7 @@ module.exports = class Auth {
|
|
|
99
98
|
* @param {string} authObject.client_secret Client Secret from SFMC config
|
|
100
99
|
* @param {number} authObject.account_id MID of Business Unit used for API Calls
|
|
101
100
|
* @param {string} authObject.auth_url Auth URL from SFMC config
|
|
102
|
-
* @param {
|
|
101
|
+
* @param {string[]} [authObject.scope] Array of scopes used for requests
|
|
103
102
|
* @param {object} options options for the SDK as a whole, for example collection of handler functions, or retry settings
|
|
104
103
|
*/
|
|
105
104
|
constructor(authObject, options) {
|
|
@@ -108,23 +107,23 @@ module.exports = class Auth {
|
|
|
108
107
|
} else if (!authObject.client_id) {
|
|
109
108
|
throw new Error('client_id or client_secret is missing or invalid');
|
|
110
109
|
} else if (typeof authObject.client_id !== 'string') {
|
|
111
|
-
throw new
|
|
110
|
+
throw new TypeError('client_id or client_secret must be strings');
|
|
112
111
|
} else if (!authObject.client_secret) {
|
|
113
112
|
throw new Error('client_id or client_secret is missing or invalid');
|
|
114
113
|
} else if (typeof authObject.client_secret !== 'string') {
|
|
115
|
-
throw new
|
|
114
|
+
throw new TypeError('client_id or client_secret must be strings');
|
|
116
115
|
} else if (!authObject.account_id) {
|
|
117
116
|
throw new Error('account_id is missing or invalid');
|
|
118
117
|
} else if (!Number.isInteger(Number.parseInt(authObject.account_id))) {
|
|
119
|
-
throw new
|
|
118
|
+
throw new TypeError(
|
|
120
119
|
'account_id must be an Integer (Integers in String format are accepted)'
|
|
121
120
|
);
|
|
122
121
|
} else if (!authObject.auth_url) {
|
|
123
122
|
throw new Error('auth_url is missing or invalid');
|
|
124
123
|
} else if (typeof authObject.auth_url !== 'string') {
|
|
125
|
-
throw new
|
|
124
|
+
throw new TypeError('auth_url must be a string');
|
|
126
125
|
} else if (
|
|
127
|
-
!/https:\/\/[
|
|
126
|
+
!/https:\/\/[\da-z-]{28}\.auth\.marketingcloudapis\.com\//gm.test(authObject.auth_url)
|
|
128
127
|
) {
|
|
129
128
|
throw new Error(
|
|
130
129
|
'auth_url must be in format https://mcXXXXXXXXXXXXXXXXXXXXXXXXXX.auth.marketingcloudapis.com/'
|
|
@@ -134,7 +133,7 @@ module.exports = class Auth {
|
|
|
134
133
|
} else if (authObject.scope && getInvalidScopes(authObject.scope).length > 0) {
|
|
135
134
|
throw new Error(
|
|
136
135
|
getInvalidScopes(authObject.scope)
|
|
137
|
-
.map((
|
|
136
|
+
.map((value) => '"' + value + '"')
|
|
138
137
|
.join(',') + ' is/are invalid scope(s)'
|
|
139
138
|
);
|
|
140
139
|
}
|
|
@@ -146,7 +145,7 @@ module.exports = class Auth {
|
|
|
146
145
|
*
|
|
147
146
|
* @param {boolean} forceRefresh used to enforce a refresh of token
|
|
148
147
|
* @param {number} remainingAttempts number of retries in case of issues
|
|
149
|
-
* @returns {Promise
|
|
148
|
+
* @returns {Promise.<object>} current session information
|
|
150
149
|
*/
|
|
151
150
|
async getAccessToken(forceRefresh, remainingAttempts) {
|
|
152
151
|
if (remainingAttempts === undefined) {
|
|
@@ -156,19 +155,19 @@ module.exports = class Auth {
|
|
|
156
155
|
try {
|
|
157
156
|
remainingAttempts--;
|
|
158
157
|
this.authObject = await _requestToken(this.authObject);
|
|
159
|
-
} catch (
|
|
158
|
+
} catch (error) {
|
|
160
159
|
if (
|
|
161
160
|
this.options.retryOnConnectionError &&
|
|
162
161
|
remainingAttempts > 0 &&
|
|
163
|
-
isConnectionError(
|
|
162
|
+
isConnectionError(error.code)
|
|
164
163
|
) {
|
|
165
164
|
if (this.options?.eventHandlers?.onConnectionError) {
|
|
166
|
-
this.options.eventHandlers.onConnectionError(
|
|
165
|
+
this.options.eventHandlers.onConnectionError(error, remainingAttempts);
|
|
167
166
|
}
|
|
168
167
|
return this.getAccessToken(forceRefresh, remainingAttempts);
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
throw new RestError(
|
|
170
|
+
throw new RestError(error);
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
if (this.options?.eventHandlers?.onRefresh) {
|
|
@@ -205,7 +204,7 @@ function _isExpired(authObject) {
|
|
|
205
204
|
}
|
|
206
205
|
/**
|
|
207
206
|
* @param {object} authObject Auth Object for api calls
|
|
208
|
-
* @returns {Promise
|
|
207
|
+
* @returns {Promise.<object>} updated Auth Object
|
|
209
208
|
*/
|
|
210
209
|
async function _requestToken(authObject) {
|
|
211
210
|
// TODO retry logic
|
|
@@ -218,19 +217,19 @@ async function _requestToken(authObject) {
|
|
|
218
217
|
if (authObject.scope && Array.isArray(authObject.scope)) {
|
|
219
218
|
payload.scope = authObject.scope.join(' ');
|
|
220
219
|
}
|
|
221
|
-
const
|
|
220
|
+
const result = await axios({
|
|
222
221
|
method: 'post',
|
|
223
222
|
baseURL: authObject.auth_url,
|
|
224
223
|
url: '/v2/token',
|
|
225
224
|
data: payload,
|
|
226
225
|
});
|
|
227
|
-
return Object.assign(authObject,
|
|
228
|
-
expiration: process.hrtime()[0] +
|
|
226
|
+
return Object.assign(authObject, result.data, {
|
|
227
|
+
expiration: process.hrtime()[0] + result.data.expires_in,
|
|
229
228
|
});
|
|
230
229
|
}
|
|
231
230
|
/**
|
|
232
|
-
* @param {
|
|
233
|
-
* @returns {
|
|
231
|
+
* @param {string[]} scopes list of scopes requested for the auth
|
|
232
|
+
* @returns {string[]} list of invalid scopes
|
|
234
233
|
*/
|
|
235
234
|
function getInvalidScopes(scopes) {
|
|
236
235
|
return scopes.filter((scope) => !AVAIALABLE_SCOPES.includes(scope));
|
package/lib/index.js
CHANGED
package/lib/rest.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
2
|
const axios = require('axios');
|
|
4
3
|
const { isObject, isPayload, isConnectionError, RestError } = require('./util');
|
|
5
4
|
const pLimit = require('p-limit');
|
|
@@ -26,7 +25,7 @@ module.exports = class Rest {
|
|
|
26
25
|
* Method that makes the GET API request
|
|
27
26
|
*
|
|
28
27
|
* @param {string} url of the resource to retrieve
|
|
29
|
-
* @returns {Promise
|
|
28
|
+
* @returns {Promise.<object>} API response
|
|
30
29
|
*/
|
|
31
30
|
get(url) {
|
|
32
31
|
return this._apiRequest(
|
|
@@ -37,51 +36,87 @@ module.exports = class Rest {
|
|
|
37
36
|
this.options.requestAttempts
|
|
38
37
|
);
|
|
39
38
|
}
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* helper for {@link getBulk} to determine if the url is a transactional message API
|
|
41
|
+
*
|
|
42
|
+
* @private
|
|
43
|
+
* @param {string} url url without query params
|
|
44
|
+
* @returns {boolean} true if the url is a transactional message API
|
|
45
|
+
*/
|
|
46
|
+
_isTransactionalMessageApi(url) {
|
|
41
47
|
return url && this.transactionalApis.some((api) => url.includes(api));
|
|
42
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* helper for {@link getBulk} to determine if the url is a legacy API
|
|
51
|
+
*
|
|
52
|
+
* @private
|
|
53
|
+
* @param {string} url url without query params
|
|
54
|
+
* @returns {boolean} true if the url is a legacy API
|
|
55
|
+
*/
|
|
56
|
+
_isLegacyApi(url) {
|
|
57
|
+
return url && url.startsWith('/legacy/v1/');
|
|
58
|
+
}
|
|
43
59
|
/**
|
|
44
60
|
* Method that makes paginated GET API Requests using $pageSize and $page parameters
|
|
45
61
|
*
|
|
46
62
|
* @param {string} url of the resource to retrieve
|
|
47
63
|
* @param {number} [pageSize] of the response, defaults to 50
|
|
48
|
-
* @
|
|
64
|
+
* @param {string} [iteratorField] attribute of the response to iterate over (only required if it's not 'items'|'definitions'|'entry')
|
|
65
|
+
* @returns {Promise.<object>} API response combined items
|
|
49
66
|
*/
|
|
50
|
-
async getBulk(url, pageSize) {
|
|
51
|
-
let iteratorField;
|
|
67
|
+
async getBulk(url, pageSize, iteratorField) {
|
|
52
68
|
let page = 1;
|
|
53
69
|
const baseUrl = url.split('?')[0];
|
|
54
|
-
const isTransactionalMessageApi = this.
|
|
55
|
-
const
|
|
70
|
+
const isTransactionalMessageApi = this._isTransactionalMessageApi(baseUrl);
|
|
71
|
+
const isLegacyApi = this._isLegacyApi(baseUrl);
|
|
72
|
+
const queryParameters = new URLSearchParams(url.split('?')[1]);
|
|
56
73
|
let collector;
|
|
57
74
|
let shouldPaginate = false;
|
|
58
|
-
|
|
75
|
+
let pageSizeKey = '$pageSize';
|
|
76
|
+
let pageKey = '$page';
|
|
77
|
+
let countKey = 'count';
|
|
78
|
+
if (isLegacyApi) {
|
|
79
|
+
pageSizeKey = '$top';
|
|
80
|
+
pageKey = '$skip';
|
|
81
|
+
countKey = 'totalResults';
|
|
82
|
+
page = 0; // legacy index starts with 0
|
|
83
|
+
if (pageSize != 50) {
|
|
84
|
+
// values other than 50 are ignored by at least some of the sub-endpoints; while others have 50 as the maximum.
|
|
85
|
+
pageSize = 50;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
queryParameters.set(pageSizeKey, Number(pageSize || 50).toString());
|
|
59
89
|
do {
|
|
60
|
-
|
|
61
|
-
const
|
|
90
|
+
queryParameters.set(pageKey, Number(page).toString());
|
|
91
|
+
const responseBatch = await this._apiRequest(
|
|
62
92
|
{
|
|
63
93
|
method: 'GET',
|
|
64
|
-
url: baseUrl + '?' + decodeURIComponent(
|
|
94
|
+
url: baseUrl + '?' + decodeURIComponent(queryParameters.toString()),
|
|
65
95
|
},
|
|
66
96
|
this.options.requestAttempts
|
|
67
97
|
);
|
|
68
|
-
if (Array.isArray(
|
|
98
|
+
if (iteratorField && Array.isArray(responseBatch[iteratorField])) {
|
|
99
|
+
// if the iteratorField is set, use it
|
|
100
|
+
} else if (Array.isArray(responseBatch.items)) {
|
|
69
101
|
iteratorField = 'items';
|
|
70
|
-
} else if (Array.isArray(
|
|
102
|
+
} else if (Array.isArray(responseBatch.definitions)) {
|
|
71
103
|
iteratorField = 'definitions';
|
|
104
|
+
} else if (Array.isArray(responseBatch.entry)) {
|
|
105
|
+
iteratorField = 'entry';
|
|
72
106
|
} else {
|
|
73
|
-
throw new
|
|
107
|
+
throw new TypeError('Could not find an array to iterate over');
|
|
74
108
|
}
|
|
75
|
-
if (collector && Array.isArray(
|
|
76
|
-
collector[iteratorField].push(...
|
|
77
|
-
} else if (collector
|
|
78
|
-
collector =
|
|
109
|
+
if (collector && Array.isArray(responseBatch[iteratorField])) {
|
|
110
|
+
collector[iteratorField].push(...responseBatch[iteratorField]);
|
|
111
|
+
} else if (!collector) {
|
|
112
|
+
collector = responseBatch;
|
|
79
113
|
}
|
|
80
114
|
if (
|
|
81
115
|
Array.isArray(collector[iteratorField]) &&
|
|
82
|
-
collector[iteratorField].length >=
|
|
116
|
+
collector[iteratorField].length >= responseBatch[countKey] &&
|
|
83
117
|
(!isTransactionalMessageApi ||
|
|
84
|
-
(isTransactionalMessageApi &&
|
|
118
|
+
(isTransactionalMessageApi &&
|
|
119
|
+
responseBatch[countKey] != responseBatch[pageSizeKey]))
|
|
85
120
|
) {
|
|
86
121
|
// ! the transactional message API returns a value for "count" that represents the currently returned number of records, instead of the total amount. checking for count != pageSize is a workaround for this
|
|
87
122
|
// * opened Support Case #43988240 for this issue
|
|
@@ -90,6 +125,8 @@ module.exports = class Rest {
|
|
|
90
125
|
page++;
|
|
91
126
|
shouldPaginate = true;
|
|
92
127
|
if (this.options?.eventHandlers?.onLoop) {
|
|
128
|
+
// TODO in v1 change to undefined to ensure breaking changes considered
|
|
129
|
+
// eslint-disable-next-line unicorn/no-null
|
|
93
130
|
this.options.eventHandlers.onLoop(null, collector?.[iteratorField]);
|
|
94
131
|
}
|
|
95
132
|
}
|
|
@@ -99,9 +136,9 @@ module.exports = class Rest {
|
|
|
99
136
|
/**
|
|
100
137
|
* Method that makes a GET API request for each URL (including rate limiting)
|
|
101
138
|
*
|
|
102
|
-
* @param {
|
|
139
|
+
* @param {string[]} urlArray of the resource to retrieve
|
|
103
140
|
* @param {number} [concurrentLimit=5] number of requests to execute at once
|
|
104
|
-
* @returns {Promise
|
|
141
|
+
* @returns {Promise.<Array>} API response
|
|
105
142
|
*/
|
|
106
143
|
async getCollection(urlArray, concurrentLimit) {
|
|
107
144
|
const limit = pLimit(concurrentLimit || 5);
|
|
@@ -126,7 +163,7 @@ module.exports = class Rest {
|
|
|
126
163
|
*
|
|
127
164
|
* @param {string} url of the resource to create
|
|
128
165
|
* @param {object} payload for the POST request body
|
|
129
|
-
* @returns {Promise
|
|
166
|
+
* @returns {Promise.<object>} API response
|
|
130
167
|
*/
|
|
131
168
|
post(url, payload) {
|
|
132
169
|
const requestOptions = {
|
|
@@ -142,7 +179,7 @@ module.exports = class Rest {
|
|
|
142
179
|
*
|
|
143
180
|
* @param {string} url of the resource to replace
|
|
144
181
|
* @param {object} payload for the PUT request body
|
|
145
|
-
* @returns {Promise
|
|
182
|
+
* @returns {Promise.<object>} API response
|
|
146
183
|
*/
|
|
147
184
|
put(url, payload) {
|
|
148
185
|
const requestOptions = {
|
|
@@ -158,7 +195,7 @@ module.exports = class Rest {
|
|
|
158
195
|
*
|
|
159
196
|
* @param {string} url of the resource to update
|
|
160
197
|
* @param {object} payload for the PATCH request body
|
|
161
|
-
* @returns {Promise
|
|
198
|
+
* @returns {Promise.<object>} API response
|
|
162
199
|
*/
|
|
163
200
|
patch(url, payload) {
|
|
164
201
|
const requestOptions = {
|
|
@@ -173,7 +210,7 @@ module.exports = class Rest {
|
|
|
173
210
|
* Method that makes the DELETE api request
|
|
174
211
|
*
|
|
175
212
|
* @param {string} url of the resource to delete
|
|
176
|
-
* @returns {Promise
|
|
213
|
+
* @returns {Promise.<object>} API response
|
|
177
214
|
*/
|
|
178
215
|
delete(url) {
|
|
179
216
|
return this._apiRequest(
|
|
@@ -190,7 +227,7 @@ module.exports = class Rest {
|
|
|
190
227
|
*
|
|
191
228
|
* @param {object} requestOptions configuration for the request including body
|
|
192
229
|
* @param {number} remainingAttempts number of times this request should be reattempted in case of error
|
|
193
|
-
* @returns {Promise
|
|
230
|
+
* @returns {Promise.<object>} Results from the Rest request in Object format
|
|
194
231
|
*/
|
|
195
232
|
async _apiRequest(requestOptions, remainingAttempts) {
|
|
196
233
|
if (!isObject(requestOptions)) {
|
|
@@ -214,9 +251,9 @@ module.exports = class Rest {
|
|
|
214
251
|
});
|
|
215
252
|
}
|
|
216
253
|
return response.data;
|
|
217
|
-
} catch (
|
|
254
|
+
} catch (error) {
|
|
218
255
|
if (requestOptions.url) {
|
|
219
|
-
|
|
256
|
+
error.endpoint =
|
|
220
257
|
requestOptions.baseURL +
|
|
221
258
|
(requestOptions.url.startsWith('/')
|
|
222
259
|
? requestOptions.url.slice(1)
|
|
@@ -225,19 +262,19 @@ module.exports = class Rest {
|
|
|
225
262
|
if (
|
|
226
263
|
this.options.retryOnConnectionError &&
|
|
227
264
|
remainingAttempts > 0 &&
|
|
228
|
-
isConnectionError(
|
|
265
|
+
isConnectionError(error.code)
|
|
229
266
|
) {
|
|
230
267
|
if (this.options?.eventHandlers?.onConnectionError) {
|
|
231
|
-
this.options.eventHandlers.onConnectionError(
|
|
268
|
+
this.options.eventHandlers.onConnectionError(error, remainingAttempts);
|
|
232
269
|
}
|
|
233
270
|
return this._apiRequest(requestOptions, remainingAttempts);
|
|
234
|
-
} else if (
|
|
271
|
+
} else if (error.response && error.response.status === 401 && remainingAttempts) {
|
|
235
272
|
// force refresh due to url related issue
|
|
236
273
|
await this.auth.getAccessToken(true);
|
|
237
274
|
//only retry once on refresh since there should be no reason for this token to be invalid
|
|
238
275
|
return this._apiRequest(requestOptions, 1);
|
|
239
276
|
} else {
|
|
240
|
-
throw new RestError(
|
|
277
|
+
throw new RestError(error);
|
|
241
278
|
}
|
|
242
279
|
}
|
|
243
280
|
}
|