@ouroboros/body 1.3.0 → 1.3.1
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/lib/index.js +68 -58
- package/ouroboros-body-1.3.1.tgz +0 -0
- package/package.json +1 -1
- package/releases.md +4 -0
- package/src/index.ts +100 -80
package/lib/index.js
CHANGED
|
@@ -21,6 +21,8 @@ const METHODS = {
|
|
|
21
21
|
read: 'GET',
|
|
22
22
|
update: 'PUT'
|
|
23
23
|
};
|
|
24
|
+
// 30 second timeout
|
|
25
|
+
const REQUEST_TIMEOUT = 30000;
|
|
24
26
|
/**
|
|
25
27
|
* Body
|
|
26
28
|
*
|
|
@@ -80,6 +82,11 @@ class Body {
|
|
|
80
82
|
* @param data The data associated with the request
|
|
81
83
|
*/
|
|
82
84
|
request(action, service, noun, data) {
|
|
85
|
+
// If no error handler was assigned
|
|
86
|
+
if (!this.error) {
|
|
87
|
+
throw new Error('Must assign an error handler via body.on(\'error\', handler)' +
|
|
88
|
+
' or body.onError(handler)');
|
|
89
|
+
}
|
|
83
90
|
// Generate the URL for the request
|
|
84
91
|
let url = `https://${this._domain}/${service}/${noun}`;
|
|
85
92
|
// Init the response and json variables
|
|
@@ -98,24 +105,31 @@ class Body {
|
|
|
98
105
|
}
|
|
99
106
|
// Set this
|
|
100
107
|
const _ = this;
|
|
101
|
-
// Handles an error based on whether an error handler is set
|
|
102
|
-
function handleError(message) {
|
|
103
|
-
if (_.error) {
|
|
104
|
-
_.error(message, { action, data, url });
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
throw new Error(message);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
108
|
// Create a new Promise and return it
|
|
111
109
|
return new Promise((resolve, reject) => {
|
|
110
|
+
// Handle errors the same way every time
|
|
111
|
+
function handleError(reason) {
|
|
112
|
+
// Generate the error message
|
|
113
|
+
const err = `${METHODS[action]} ${url} ${reason}`;
|
|
114
|
+
// Call the error handler
|
|
115
|
+
_.error(err, { action, data, url });
|
|
116
|
+
// Return reject path
|
|
117
|
+
return reject({ code: 0, msg: err });
|
|
118
|
+
}
|
|
119
|
+
// Create an abort controller for timeouts
|
|
120
|
+
const controller = new AbortController();
|
|
112
121
|
// Init the fetch init
|
|
113
122
|
const fetchInit = {
|
|
114
|
-
method: METHODS[action],
|
|
115
123
|
headers: {
|
|
116
124
|
'Content-Type': 'application/json; charset=utf-8'
|
|
117
|
-
}
|
|
125
|
+
},
|
|
126
|
+
method: METHODS[action],
|
|
127
|
+
signal: controller.signal
|
|
118
128
|
};
|
|
129
|
+
// Start a timer so we timeout if the request doesn't return
|
|
130
|
+
const timer = setTimeout(() => {
|
|
131
|
+
controller.abort();
|
|
132
|
+
}, REQUEST_TIMEOUT);
|
|
119
133
|
// If we have a session token, add it as the Authorization header
|
|
120
134
|
if (_.token) {
|
|
121
135
|
fetchInit.headers.Authorization = _.token;
|
|
@@ -135,12 +149,46 @@ class Body {
|
|
|
135
149
|
// If the Content-Type is missing or invalid
|
|
136
150
|
const ct = response.headers.get('Content-Type');
|
|
137
151
|
if (!ct || ct !== 'application/json; charset=utf-8') {
|
|
138
|
-
|
|
139
|
-
handleError(err);
|
|
140
|
-
return reject({ code: 0, msg: err });
|
|
152
|
+
return handleError(`returned invalid Content-Type: "${ct}"`);
|
|
141
153
|
}
|
|
142
154
|
// Return the JSON
|
|
143
|
-
return response.json()
|
|
155
|
+
return response.json().then(result => {
|
|
156
|
+
// If there's no result, return an error + reject
|
|
157
|
+
if (!result) {
|
|
158
|
+
return handleError('returned: empty JSON');
|
|
159
|
+
}
|
|
160
|
+
// Set res
|
|
161
|
+
res = result;
|
|
162
|
+
// Init the structure to pass to resolve
|
|
163
|
+
const oResult = {};
|
|
164
|
+
// If we got an error
|
|
165
|
+
if ('error' in result && result.error) {
|
|
166
|
+
// If we don't have an onErrorCode callback, or we
|
|
167
|
+
// do and calling it returns false
|
|
168
|
+
if (!_.errorCode ||
|
|
169
|
+
_.errorCode(result.error, { action, data, res, url }) === false) {
|
|
170
|
+
// If it wasn't handled, add it to the result
|
|
171
|
+
oResult.error = structuredClone(result.error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// If we got a warning
|
|
175
|
+
if ('warning' in result && result.warning) {
|
|
176
|
+
// If we don't have an onWarning callback, or we do
|
|
177
|
+
// and calling it returns false
|
|
178
|
+
if (!_.warning ||
|
|
179
|
+
_.warning(result.warning, { action, data, res, url }) === false) {
|
|
180
|
+
// If it wasn't handled, add it to the result
|
|
181
|
+
oResult.warning = structuredClone(result.warning);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// If we got data
|
|
185
|
+
if ('data' in result) {
|
|
186
|
+
// Add it
|
|
187
|
+
oResult.data = structuredClone(result.data);
|
|
188
|
+
}
|
|
189
|
+
// Resolve
|
|
190
|
+
return resolve(oResult);
|
|
191
|
+
});
|
|
144
192
|
}
|
|
145
193
|
// If it's 401
|
|
146
194
|
else if (response.status === 401) {
|
|
@@ -149,53 +197,15 @@ class Body {
|
|
|
149
197
|
_.noSession();
|
|
150
198
|
}
|
|
151
199
|
// Reject the request
|
|
152
|
-
return
|
|
200
|
+
return handleError('returned: 401 NOT AUTHORIZED');
|
|
153
201
|
}
|
|
154
202
|
// Else, invalid status
|
|
155
|
-
|
|
156
|
-
const err = `${METHODS[action]} ${url} returned invalid status: ${response.status}`;
|
|
157
|
-
handleError(err);
|
|
158
|
-
return reject({ code: 0, msg: err });
|
|
159
|
-
}
|
|
160
|
-
}).then(result => {
|
|
161
|
-
// If there's no result, do nothing
|
|
162
|
-
if (!result) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
// Set res
|
|
166
|
-
res = result;
|
|
167
|
-
// Init the structure to pass to resolve
|
|
168
|
-
const oResult = {};
|
|
169
|
-
// If we got an error
|
|
170
|
-
if ('error' in result && result.error) {
|
|
171
|
-
// If we don't have an onErrorCode callback, or we do and
|
|
172
|
-
// calling it returns false
|
|
173
|
-
if (!_.errorCode ||
|
|
174
|
-
_.errorCode(result.error, { action, data, res, url }) === false) {
|
|
175
|
-
// If it wasn't handled, add it to the result
|
|
176
|
-
oResult.error = structuredClone(result.error);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// If we got a warning and we have an onWarning callback
|
|
180
|
-
if ('warning' in result && result.warning && _.warning) {
|
|
181
|
-
// If we don't have an onWarning callback, or we do and
|
|
182
|
-
// calling it returns false
|
|
183
|
-
if (!_.warning ||
|
|
184
|
-
_.warning(result.warning, { action, data, res, url }) === false) {
|
|
185
|
-
// If it wasn't handled, add it to the result
|
|
186
|
-
oResult.warning = structuredClone(result.warning);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// If we got data
|
|
190
|
-
if ('data' in result) {
|
|
191
|
-
// Add it
|
|
192
|
-
oResult.data = structuredClone(result.data);
|
|
193
|
-
}
|
|
194
|
-
// Resolve
|
|
195
|
-
return resolve(oResult);
|
|
203
|
+
return handleError(`returned: invalid status ${response.status}`);
|
|
196
204
|
}).catch(reason => {
|
|
197
|
-
handleError(
|
|
205
|
+
return handleError(`failed with: "${String(reason)}"`);
|
|
198
206
|
}).finally(() => {
|
|
207
|
+
// Clear the timeout timer
|
|
208
|
+
clearTimeout(timer);
|
|
199
209
|
// If we have a requested callback
|
|
200
210
|
if (this.requested) {
|
|
201
211
|
this.requested({ action, data, res, url });
|
|
Binary file
|
package/package.json
CHANGED
package/releases.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @ouroboros/body releases
|
|
2
2
|
|
|
3
|
+
## 1.3.1
|
|
4
|
+
- Fixed issues with requests potentially not resolving promises on errors.
|
|
5
|
+
- Added 30 second timeout on requests so they don't hang forever.
|
|
6
|
+
|
|
3
7
|
## 1.3.0
|
|
4
8
|
- Stopped splitting data and error into resolve and reject as this made it harder to identify and solve real communication problems. From now on all requests that are returned successfully, regardless of the result, are returned in full to resolve. This means all current `.then()` calls on requests must handle the full responseStruct result instead of just the `.data`. Similarly, any code currently running in the reject based on completed, but error filled, requests must be moved into the first argument of `.then()`.
|
|
5
9
|
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,9 @@ const METHODS = {
|
|
|
63
63
|
update: 'PUT'
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// 30 second timeout
|
|
67
|
+
const REQUEST_TIMEOUT = 30000;
|
|
68
|
+
|
|
66
69
|
/**
|
|
67
70
|
* Body
|
|
68
71
|
*
|
|
@@ -140,6 +143,14 @@ class Body {
|
|
|
140
143
|
data: any
|
|
141
144
|
): Promise<any> {
|
|
142
145
|
|
|
146
|
+
// If no error handler was assigned
|
|
147
|
+
if(!this.error) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
'Must assign an error handler via body.on(\'error\', handler)' +
|
|
150
|
+
' or body.onError(handler)'
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
143
154
|
// Generate the URL for the request
|
|
144
155
|
let url = `https://${this._domain}/${service}/${noun}`;
|
|
145
156
|
|
|
@@ -162,26 +173,39 @@ class Body {
|
|
|
162
173
|
// Set this
|
|
163
174
|
const _ = this;
|
|
164
175
|
|
|
165
|
-
// Handles an error based on whether an error handler is set
|
|
166
|
-
function handleError(message: string): void {
|
|
167
|
-
if(_.error) {
|
|
168
|
-
_.error(message, { action, data, url });
|
|
169
|
-
} else {
|
|
170
|
-
throw new Error(message);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
176
|
// Create a new Promise and return it
|
|
175
177
|
return new Promise((resolve: responseResolve, reject) => {
|
|
176
178
|
|
|
179
|
+
// Handle errors the same way every time
|
|
180
|
+
function handleError(reason: string): void {
|
|
181
|
+
|
|
182
|
+
// Generate the error message
|
|
183
|
+
const err: string = `${METHODS[action]} ${url} ${reason}`;
|
|
184
|
+
|
|
185
|
+
// Call the error handler
|
|
186
|
+
(_.error as onError)(err, { action, data, url });
|
|
187
|
+
|
|
188
|
+
// Return reject path
|
|
189
|
+
return reject({ code: 0, msg: err });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Create an abort controller for timeouts
|
|
193
|
+
const controller = new AbortController()
|
|
194
|
+
|
|
177
195
|
// Init the fetch init
|
|
178
196
|
const fetchInit: any = {
|
|
179
|
-
method: METHODS[action],
|
|
180
197
|
headers: {
|
|
181
198
|
'Content-Type': 'application/json; charset=utf-8'
|
|
182
|
-
}
|
|
199
|
+
},
|
|
200
|
+
method: METHODS[action],
|
|
201
|
+
signal: controller.signal
|
|
183
202
|
}
|
|
184
203
|
|
|
204
|
+
// Start a timer so we timeout if the request doesn't return
|
|
205
|
+
const timer = setTimeout(() => {
|
|
206
|
+
controller.abort();
|
|
207
|
+
}, REQUEST_TIMEOUT);
|
|
208
|
+
|
|
185
209
|
// If we have a session token, add it as the Authorization header
|
|
186
210
|
if(_.token) {
|
|
187
211
|
fetchInit.headers.Authorization = _.token;
|
|
@@ -206,13 +230,67 @@ class Body {
|
|
|
206
230
|
// If the Content-Type is missing or invalid
|
|
207
231
|
const ct = response.headers.get('Content-Type');
|
|
208
232
|
if(!ct || ct !== 'application/json; charset=utf-8') {
|
|
209
|
-
|
|
210
|
-
handleError(err);
|
|
211
|
-
return reject({ code: 0, msg: err });
|
|
233
|
+
return handleError(`returned invalid Content-Type: "${ct}"`);
|
|
212
234
|
}
|
|
213
235
|
|
|
214
236
|
// Return the JSON
|
|
215
|
-
return response.json()
|
|
237
|
+
return response.json().then(result => {
|
|
238
|
+
|
|
239
|
+
// If there's no result, return an error + reject
|
|
240
|
+
if(!result) {
|
|
241
|
+
return handleError('returned: empty JSON');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Set res
|
|
245
|
+
res = result;
|
|
246
|
+
|
|
247
|
+
// Init the structure to pass to resolve
|
|
248
|
+
const oResult: responseStruct = { };
|
|
249
|
+
|
|
250
|
+
// If we got an error
|
|
251
|
+
if('error' in result && result.error) {
|
|
252
|
+
|
|
253
|
+
// If we don't have an onErrorCode callback, or we
|
|
254
|
+
// do and calling it returns false
|
|
255
|
+
if(!_.errorCode ||
|
|
256
|
+
_.errorCode(
|
|
257
|
+
result.error as responseErrorStruct,
|
|
258
|
+
{ action, data, res, url }
|
|
259
|
+
) === false
|
|
260
|
+
) {
|
|
261
|
+
// If it wasn't handled, add it to the result
|
|
262
|
+
oResult.error = structuredClone(result.error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// If we got a warning
|
|
267
|
+
if('warning' in result && result.warning) {
|
|
268
|
+
|
|
269
|
+
// If we don't have an onWarning callback, or we do
|
|
270
|
+
// and calling it returns false
|
|
271
|
+
if(!_.warning ||
|
|
272
|
+
_.warning(
|
|
273
|
+
result.warning,
|
|
274
|
+
{ action, data, res, url }
|
|
275
|
+
) === false
|
|
276
|
+
) {
|
|
277
|
+
// If it wasn't handled, add it to the result
|
|
278
|
+
oResult.warning = structuredClone(
|
|
279
|
+
result.warning
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// If we got data
|
|
285
|
+
if('data' in result) {
|
|
286
|
+
|
|
287
|
+
// Add it
|
|
288
|
+
oResult.data = structuredClone(result.data);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Resolve
|
|
292
|
+
return resolve(oResult);
|
|
293
|
+
});
|
|
216
294
|
}
|
|
217
295
|
|
|
218
296
|
// If it's 401
|
|
@@ -224,77 +302,19 @@ class Body {
|
|
|
224
302
|
}
|
|
225
303
|
|
|
226
304
|
// Reject the request
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
// Else, invalid status
|
|
230
|
-
else {
|
|
231
|
-
const err = `${METHODS[action]} ${url} returned invalid status: ${response.status}`;
|
|
232
|
-
handleError(err);
|
|
233
|
-
return reject({ code: 0, msg: err });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
}).then(result => {
|
|
237
|
-
|
|
238
|
-
// If there's no result, do nothing
|
|
239
|
-
if(!result) {
|
|
240
|
-
return;
|
|
305
|
+
return handleError('returned: 401 NOT AUTHORIZED');
|
|
241
306
|
}
|
|
242
307
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// Init the structure to pass to resolve
|
|
247
|
-
const oResult: responseStruct = { };
|
|
248
|
-
|
|
249
|
-
// If we got an error
|
|
250
|
-
if('error' in result && result.error) {
|
|
251
|
-
|
|
252
|
-
// If we don't have an onErrorCode callback, or we do and
|
|
253
|
-
// calling it returns false
|
|
254
|
-
if(!_.errorCode ||
|
|
255
|
-
_.errorCode(
|
|
256
|
-
result.error as responseErrorStruct,
|
|
257
|
-
{ action, data, res, url }
|
|
258
|
-
) === false
|
|
259
|
-
) {
|
|
260
|
-
// If it wasn't handled, add it to the result
|
|
261
|
-
oResult.error = structuredClone(result.error);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// If we got a warning and we have an onWarning callback
|
|
266
|
-
if('warning' in result && result.warning && _.warning) {
|
|
267
|
-
|
|
268
|
-
// If we don't have an onWarning callback, or we do and
|
|
269
|
-
// calling it returns false
|
|
270
|
-
if(!_.warning ||
|
|
271
|
-
_.warning(
|
|
272
|
-
result.warning,
|
|
273
|
-
{ action, data, res, url }
|
|
274
|
-
) === false
|
|
275
|
-
) {
|
|
276
|
-
// If it wasn't handled, add it to the result
|
|
277
|
-
oResult.warning = structuredClone(result.warning);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// If we got data
|
|
282
|
-
if('data' in result) {
|
|
283
|
-
|
|
284
|
-
// Add it
|
|
285
|
-
oResult.data = structuredClone(result.data);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Resolve
|
|
289
|
-
return resolve(oResult);
|
|
308
|
+
// Else, invalid status
|
|
309
|
+
return handleError(`returned: invalid status ${response.status}`);
|
|
290
310
|
|
|
291
311
|
}).catch(reason => {
|
|
292
|
-
handleError(
|
|
293
|
-
`${METHODS[action]} ${url} failed because of "${reason}"`
|
|
294
|
-
)
|
|
295
|
-
|
|
312
|
+
return handleError(`failed with: "${String(reason)}"`);
|
|
296
313
|
}).finally(() => {
|
|
297
314
|
|
|
315
|
+
// Clear the timeout timer
|
|
316
|
+
clearTimeout(timer);
|
|
317
|
+
|
|
298
318
|
// If we have a requested callback
|
|
299
319
|
if(this.requested) {
|
|
300
320
|
this.requested({ action, data, res, url });
|