@63klabs/cache-data 1.2.2

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.
@@ -0,0 +1,673 @@
1
+ // This file is used to make API requests and handle responses
2
+
3
+ const https = require('https');
4
+ const {AWS, AWSXRay} = require('./AWS.classes.js');
5
+ const DebugAndLog = require('./DebugAndLog.class.js');
6
+
7
+ /* Either return an XRay segment or mock one up so we don't need much logic if xray isn't used */
8
+ const xRayProxyFunc = {
9
+ addMetadata: (mockParam, mockObj) => { DebugAndLog.debug(`Mocking XRay addMetadata: ${mockParam} | ${mockObj}`); },
10
+ addAnnotation: (mockParam, mockObj) => { DebugAndLog.debug(`Mocking XRay addAnnotation: ${mockParam} | ${mockObj}`); },
11
+ addError: (mockError) => { DebugAndLog.debug(`Mocking XRay addError: ${mockError}`); },
12
+ addFaultFlag: () => { DebugAndLog.debug(`Mocking XRay addFaultFlag`); },
13
+ addErrorFlag: () => { DebugAndLog.debug(`Mocking XRay addErrorFlag`); },
14
+ close: () => { DebugAndLog.debug(`Mocking XRay close`); }
15
+ };
16
+
17
+ const xRayProxy = (AWSXRay !== null) ? AWS.XRay.getSegment() : {
18
+ ...xRayProxyFunc,
19
+ addNewSubsegment: (mockString) => {
20
+ DebugAndLog.debug(`Mocking XRay subsegment: ${mockString}`);
21
+ return xRayProxyFunc;
22
+ }
23
+ };
24
+
25
+ /**
26
+ * An internal tools function used by APIRequest. https.get does not work well
27
+ * inside a class object (specifically doesn't like this.*), so we make it
28
+ * external to the class and pass the class as a reference to be updated either
29
+ * with a response or redirect uri.
30
+ * @param {object} options The options object for https.get()
31
+ * @param {APIRequest} requestObject The APIRequest object that contains internal functions, request info (including uri) and redirects. This object will be updated with any redirects and responses
32
+ * @returns A promise that will resolve to a boolean denoting whether or not the response is considered complete (no unresolved redirects). The boolean does not mean "error free." Even if we receive errors it is considered complete.
33
+ */
34
+ const _httpGetExecute = async function (options, requestObject, xRaySegment = xRayProxy) {
35
+
36
+ /*
37
+ Return a promise that will resolve to true or false based upon success
38
+ */
39
+ return new Promise ((resolve, reject) => {
40
+
41
+ /*
42
+ Functions/variables we'll use within https.get()
43
+ We need to declare functions that we will be using within https.get()
44
+ "locally" and refer to the requestObject to perform updates
45
+ setResponse() and addRedirect() also performs the resolve() and reject() for the promise
46
+ */
47
+ const setResponse = function (response) { requestObject.setResponse(response); resolve(true)};
48
+ const addRedirect = function (uri) { requestObject.addRedirect(uri); resolve(false)};
49
+ const redirects = requestObject.getNumberOfRedirects();
50
+ const uri = requestObject.getURI();
51
+
52
+ let body = "";
53
+
54
+ /*
55
+ Perform the https.get()
56
+ */
57
+ let req = https.request(uri, options, (res) => {
58
+
59
+ DebugAndLog.debug(`Performing https.get callback on response with status code: ${res.statusCode} ${new URL(uri).host}`);
60
+
61
+ try {
62
+
63
+ /*
64
+ - IF it is a redirect, then add the redirect to the request object
65
+ and it will not be marked as complete.
66
+
67
+ - ELSE we'll update the request object with the response which
68
+ will mark it as complete.
69
+ */
70
+ if ( res.statusCode === 301
71
+ || res.statusCode === 302
72
+ || res.statusCode === 303
73
+ || res.statusCode === 307
74
+ )
75
+ {
76
+
77
+ DebugAndLog.debug(`Processing a redirect: ${res.statusCode}`);
78
+
79
+ /*
80
+ - IF We have not performed the max number of redirects then we
81
+ will process the current redirect.
82
+
83
+ - ELSE We will produce an error of too many redirects.
84
+ */
85
+ if ( redirects < APIRequest.MAX_REDIRECTS ) {
86
+
87
+ // we'll gather variables to use in logging
88
+ let newLocation = res.headers.location;
89
+ let nloc = new URL(newLocation);
90
+ let rloc = new URL(uri);
91
+
92
+ /* log essential as warning, others only when debugging
93
+ Note that we only list the hostname and path because
94
+ sensitive info may be in the query string. Also,
95
+ redirects typically don't involve query strings anyway */
96
+ if (res.statusCode === 301) {
97
+ // report as this as permanent, we'll want to report this in the log as a warning and fix
98
+ DebugAndLog.warning("301 | Redirect (Moved Permanently) received", {requested: rloc.protocol +"//"+ rloc.hostname + rloc.pathname, redirect: nloc.protocol +"//"+ nloc.hostname + nloc.pathname });
99
+ } else {
100
+ // Temporary redirect, just follow it, don't if we are not in debug mode
101
+ DebugAndLog.debug(res.statusCode+" Redirect received", {requested: rloc.protocol +"//"+ rloc.hostname + rloc.pathname, redirect: nloc.protocol +"//"+ nloc.hostname + nloc.pathname })
102
+ }
103
+
104
+ // don't let the redirect downgrade
105
+ if ( rloc.protocol === "https:" && nloc.protocol !== "https:") { // URL() protocol has the colon 'https:'
106
+ newLocation = newLocation.replace("http:","https:");
107
+ DebugAndLog.debug("We requested https but are being redirected to http. Upgrading back to https - "+newLocation);
108
+ };
109
+
110
+ // update the request object with the redirect so it can be reprocessed
111
+ DebugAndLog.debug("Setting uri to "+newLocation);
112
+ addRedirect(newLocation);
113
+
114
+ } else {
115
+ DebugAndLog.warn(`Too many redirects. Limit of ${APIRequest.MAX_REDIRECTS}`);
116
+ setResponse(APIRequest.responseFormat(false, 500, "Too many redirects"));
117
+ }
118
+
119
+ } else {
120
+
121
+ /*
122
+ - IF we receive a 304 (not modified) then send that back as
123
+ a response. (Protocol is to return a null body)
124
+
125
+ - ELSE process as usual
126
+ */
127
+ if (res.statusCode === 304) {
128
+ // 304 not modified
129
+ DebugAndLog.debug("304 Not Modified. Setting body to null");
130
+ setResponse(APIRequest.responseFormat(
131
+ true,
132
+ res.statusCode,
133
+ "SUCCESS",
134
+ res.headers,
135
+ null));
136
+ } else {
137
+
138
+ DebugAndLog.debug("No 'Redirect' or 'Not Modified' received. Processing http get as usual");
139
+
140
+ /*
141
+ The 3 classic https.get() functions
142
+ What to do on "data", "end" and "error"
143
+ */
144
+
145
+ res.on('data', (chunk) => { body += chunk; });
146
+
147
+ res.on('end', () => {
148
+
149
+ try {
150
+
151
+ let success = (res.statusCode < 400);
152
+
153
+ xRaySegment.addAnnotation('response_status', res.statusCode);
154
+
155
+ xRaySegment.addMetadata('http',
156
+ {
157
+ request: {
158
+ method: requestObject.getMethod(),
159
+ host: requestObject.getHost(),
160
+ url: requestObject.getURI(false)
161
+ },
162
+ response: {
163
+ status: res.statusCode,
164
+ headers: res.headers
165
+ }
166
+ }
167
+ );
168
+ // Add request data
169
+ xRaySegment.http = {
170
+ request: {
171
+ method: requestObject.getMethod(),
172
+ url: requestObject.getURI(false),
173
+ traced: true
174
+ },
175
+ response: {
176
+ status: res.statusCode,
177
+ headers: res.headers
178
+ }
179
+ };
180
+
181
+ DebugAndLog.debug(`Response status ${res.statusCode}`, {status: res.statusCode, headers: res.headers});
182
+
183
+ if (res.statusCode >= 500) {
184
+ xRaySegment.addFaultFlag();
185
+ xRaySegment.addError(new Error(`Response status ${res.statusCode}`));
186
+ // xRaySegment.close(); // we are handling in calling func
187
+ } else if (res.statusCode >= 400) {
188
+ xRaySegment.addErrorFlag();
189
+ xRaySegment.addError(new Error(`Response status ${res.statusCode}`));
190
+ // xRaySegment.close(); // we are handling in calling func
191
+ } else {
192
+ // xRaySegment.close(); // we are handling in calling func
193
+ }
194
+
195
+ setResponse(APIRequest.responseFormat(
196
+ success,
197
+ res.statusCode,
198
+ (success ? "SUCCESS" : "FAIL"),
199
+ res.headers,
200
+ body));
201
+
202
+ } catch (error) {
203
+ DebugAndLog.error(`Error during http get callback for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
204
+ xRaySegment.addError(error);
205
+ setResponse(APIRequest.responseFormat(false, 500, "https.get resulted in error"));
206
+ }
207
+ });
208
+
209
+ res.on('error', error => {
210
+ DebugAndLog.error(`API error during request/response for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
211
+ xRaySegment.addError(error);
212
+ // xRaySegment.close(); // we are handling in calling func
213
+ setResponse(APIRequest.responseFormat(false, 500, "https.get resulted in error"));
214
+ });
215
+
216
+ }
217
+ }
218
+
219
+ } catch (error) {
220
+ DebugAndLog.error(`Error during http get callback for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
221
+ xRaySegment.addError(error);
222
+ setResponse(APIRequest.responseFormat(false, 500, "https.get resulted in error"));
223
+ }
224
+
225
+ });
226
+
227
+ req.on('timeout', () => {
228
+ DebugAndLog.warn(`Endpoint request timeout reached (${requestObject.getTimeOutInMilliseconds()}ms) for host: ${requestObject.getHost()}`, {host: requestObject.getHost(), note: requestObject.getNote()});
229
+ // create a new error object to pass to xray
230
+ xRaySegment.addFaultFlag();
231
+ xRaySegment.addError(new Error("Endpoint request timeout reached"));
232
+ // xRaySegment.close(); // we are handling in calling func
233
+ setResponse(APIRequest.responseFormat(false, 504, "https.request resulted in timeout"));
234
+ req.destroy(); //req.end()
235
+
236
+ });
237
+
238
+ req.on('error', error => {
239
+ DebugAndLog.error(`API error during request for host ${requestObject.getHost()} ${requestObject.getNote()} ${error.message}`, error.stack);
240
+ xRaySegment.addFaultFlag();
241
+ xRaySegment.addError(error);
242
+ // xRaySegment.close(); // we are handling in calling func
243
+ setResponse(APIRequest.responseFormat(false, 500, "https.request resulted in error"));
244
+ });
245
+
246
+ if ( requestObject.getMethod() === "POST" && requestObject.getBody() !== null ) {
247
+ req.write(requestObject.getBody());
248
+ }
249
+ req.end();
250
+
251
+ });
252
+ };
253
+
254
+ /**
255
+ * Submit GET and POST requests and handle responses.
256
+ * This class can be used in a DAO class object within its call() method.
257
+ * @example
258
+ *
259
+ * async call() {
260
+ *
261
+ * var response = null;
262
+ *
263
+ * try {
264
+ * var apiRequest = new tools.APIRequest(this.request);
265
+ * response = await apiRequest.send();
266
+ *
267
+ * } catch (error) {
268
+ * DebugAndLog.error(`Error in call: ${error.message}`, error.stack);
269
+ * response = tools.APIRequest.responseFormat(false, 500, "Error in call()");
270
+ * }
271
+ *
272
+ * return response;
273
+ *
274
+ * };
275
+ */
276
+ class APIRequest {
277
+
278
+ static MAX_REDIRECTS = 5;
279
+
280
+ #redirects = [];
281
+ #requestComplete = false;
282
+ #response = null;
283
+ #request = null;
284
+
285
+ /**
286
+ * Function used to make an API request utilized directly or from within
287
+ * a data access object.
288
+ *
289
+ * @param {ConnectionObject} request
290
+ */
291
+ constructor(request) {
292
+ this.resetRequest();
293
+
294
+ /* We need to have a method, protocol, uri (host/domain), and parameters set
295
+ Everything else is optional */
296
+
297
+ let timeOutInMilliseconds = 8000;
298
+
299
+ /* Default values */
300
+ let req = {
301
+ method: "GET",
302
+ uri: "",
303
+ protocol: "https",
304
+ host: "",
305
+ path: "",
306
+ parameters: {},
307
+ headers: {},
308
+ body: null,
309
+ note: "",
310
+ options: {
311
+ timeout: timeOutInMilliseconds,
312
+ separateDuplicateParameters: false,
313
+ separateDuplicateParametersAppendToKey: "", // "" "[]", or "0++", "1++"
314
+ combinedDuplicateParameterDelimiter: ',' // "," or "|" or " "
315
+ }
316
+ };
317
+
318
+ /* if we have a method or protocol passed to us, set them */
319
+ if ( "method" in request && request.method !== "" && request.method !== null) { req.method = request.method.toUpperCase(); }
320
+ if ( "protocol" in request && request.protocol !== "" && request.protocol !== null) { req.protocol = request.protocol.toLowerCase(); }
321
+
322
+ if ("body" in request) { req.body = request.body; }
323
+ if ("headers" in request && request.headers !== null) { req.headers = request.headers; }
324
+ if ("note" in request) { req.note = request.note; }
325
+
326
+ // With options we want to keep our defaults so we'll use Object.assign
327
+ if ("options" in request && request.options !== null) { req.options = Object.assign(req.options, request.options); }
328
+
329
+ /* if there is no timeout set, or if it is less than 1, then set to default */
330
+ if ( !("timeout" in req.options && req.options.timeout > 0) ) {
331
+ req.options.timeout = timeOutInMilliseconds;
332
+ }
333
+
334
+ /* if we have a uri, set it, otherwise form one using host and path */
335
+ if ( "uri" in request && request.uri !== null && request.uri !== "" ) {
336
+ req.uri = request.uri;
337
+ } else if ("host" in request && request.host !== "" && request.host !== null) {
338
+ let path = ("path" in request && request.path !== null && request.path !== null) ? request.path : "";
339
+ req.uri = `${req.protocol}://${request.host}${path}`; // we use req.protocol because it is already set
340
+ }
341
+
342
+ /* if we have parameters, create a query string and append to uri */
343
+ if (
344
+ "parameters" in request
345
+ && request.parameters !== null
346
+ && (typeof request.parameters === 'object' && Object.keys(request.parameters).length !== 0)
347
+ ){
348
+
349
+ req.uri += this._queryStringFromObject(request.parameters, req.options);
350
+ }
351
+
352
+ this.#request = req;
353
+ };
354
+
355
+ _queryStringFromObject = function (parameters, options) {
356
+
357
+ let qString = [];
358
+
359
+ for (const [key,value] of Object.entries(parameters) ) {
360
+
361
+ /* if the value is an array, then we have to join into one parameter or separate into multiple key/value pairs */
362
+ if ( Array.isArray(value) ) {
363
+ let values = [];
364
+
365
+ /* apply encodeURIComponent() to each element in value array */
366
+ for (const v of value) {
367
+ values.push(encodeURIComponent(v));
368
+ }
369
+
370
+ if ( "separateDuplicateParameters" in options && options.separateDuplicateParameters === true) {
371
+ let a = "";
372
+ if ( "separateDuplicateParametersAppendToKey" in options ) {
373
+ if ( options.separateDuplicateParametersAppendToKey === '1++' || options.separateDuplicateParametersAppendToKey === '0++') {
374
+ a = (options.separateDuplicateParametersAppendToKey === "1++") ? 1 : 0;
375
+ } else {
376
+ a = options.separateDuplicateParametersAppendToKey;
377
+ }
378
+ }
379
+
380
+ for (const v of values) {
381
+ qString.push(`${key}${a}=${v}`); // we encoded above
382
+ if(Number.isInteger(a)) { a++; }
383
+ }
384
+ } else {
385
+ const delim = ("combinedDuplicateParameterDelimiter" in options && options.combinedDuplicateParameterDelimiter !== null && options.combinedDuplicateParameterDelimiter !== "") ? options.combinedDuplicateParameterDelimiter : ",";
386
+ qString.push(`${key}=${values.join(delim)}`); // we encoded above
387
+ }
388
+
389
+ } else {
390
+ qString.push(`${key}=${encodeURIComponent(value)}`);
391
+ }
392
+ }
393
+
394
+ return (qString.length > 0) ? '?'+qString.join("&") : "";
395
+ }
396
+
397
+ /**
398
+ * Clears out any redirects, completion flag, and response
399
+ */
400
+ resetRequest() {
401
+ this.#redirects = [];
402
+ this.#requestComplete = false;
403
+ this.#response = null;
404
+ };
405
+
406
+ /**
407
+ * Set the uri of the request
408
+ * @param {string} newURI
409
+ */
410
+ updateRequestURI(newURI) {
411
+ this.#request.uri = newURI;
412
+ };
413
+
414
+ /**
415
+ * Add a redirect uri to the stack
416
+ * @param {string} uri
417
+ */
418
+ addRedirect(uri) {
419
+ this.#redirects.push(uri);
420
+ this.updateRequestURI(uri);
421
+ };
422
+
423
+ /**
424
+ *
425
+ * @returns {number} Number of redirects currently experienced for this request
426
+ */
427
+ getNumberOfRedirects() {
428
+ return this.#redirects.length;
429
+ };
430
+
431
+ /**
432
+ * When the request is complete, set the response and mark as complete
433
+ * @param {object} response
434
+ */
435
+ setResponse(response) {
436
+ this.#requestComplete = true;
437
+ this.#response = response;
438
+ };
439
+
440
+ /**
441
+ * @param {boolean} includeQueryString Whether or not to include the query string in the URI - For logging purposes when you don't want to include sensitive information
442
+ * @returns {string} The current URI of the request
443
+ */
444
+ getURI(includeQueryString = true) {
445
+ return (includeQueryString) ? this.#request.uri : this.#request.uri.split("?")[0];
446
+ };
447
+
448
+ /**
449
+ *
450
+ * @returns {string|null} The body of a post request
451
+ */
452
+ getBody() {
453
+ return this.#request.body;
454
+ };
455
+
456
+ /**
457
+ *
458
+ * @returns {string} The request method
459
+ */
460
+ getMethod() {
461
+ return this.#request.method;
462
+ };
463
+
464
+ /**
465
+ *
466
+ * @returns {string} A note for troubleshooting and tracing the request
467
+ */
468
+ getNote() {
469
+ return this.#request.note;
470
+ };
471
+
472
+ /**
473
+ *
474
+ * @returns {number} ClientRequest timout in milliseconds
475
+ */
476
+ getTimeOutInMilliseconds() {
477
+ return this.#request.options.timeout;
478
+ };
479
+
480
+ /**
481
+ *
482
+ * @returns {string} The host domain submitted for the request
483
+ */
484
+ getHost() {
485
+ return (new URL(this.getURI())).host;
486
+ };
487
+
488
+ /**
489
+ * Send the request
490
+ * @returns {object}
491
+ */
492
+ async send() {
493
+
494
+ this.resetRequest();
495
+
496
+ var response = null;
497
+
498
+ switch (this.#request.method) {
499
+ case "GET":
500
+ response = await this.send_get();
501
+ break;
502
+ case "POST":
503
+ response = await this.send_get(); // this.method should already be set and is all it needs
504
+ break;
505
+ default:
506
+ break; // PUT, DELETE, etc not yet implemented
507
+ }
508
+
509
+ return response;
510
+ }
511
+
512
+ /**
513
+ * Process the request
514
+ * @returns {object} Response
515
+ */
516
+ async send_get() {
517
+
518
+ return new Promise (async (resolve, reject) => {
519
+ // https://stackoverflow.com/questions/41470296/how-to-await-and-return-the-result-of-a-http-request-so-that-multiple-request
520
+
521
+ // https://nodejs.org/api/https.html#https_https_request_url_options_callback
522
+ // https://usefulangle.com/post/170/nodejs-synchronous-http-request
523
+
524
+ try {
525
+
526
+ if ( "note" in this.#request ) {
527
+ DebugAndLog.msg("Sending request for: "+this.#request.note);
528
+ }
529
+
530
+ // create the options object, use either options passed in, or an empty one
531
+ let options = ( this.#request.options !== null) ? this.#request.options : {};
532
+
533
+ // add the request headers to options
534
+ if ( this.#request.headers !== null ) {
535
+ options.headers = this.#request.headers;
536
+ }
537
+
538
+ // add the request method to options (default is GET)
539
+ if ( this.#request.method === null || this.#request.method === "") {
540
+ this.#request.method = "GET";
541
+ }
542
+ options.method = this.#request.method;
543
+
544
+ try {
545
+
546
+ // we will want to follow redirects, so keep submitting until considered complete
547
+ while ( !this.#requestComplete ) {
548
+ if (AWSXRay) {
549
+
550
+ // async send() {
551
+ // const parentSegment = AWSXRay.getSegment();
552
+ // const customSegment = new AWSXRay.Segment('APIRequest');
553
+
554
+ // return await AWSXRay.captureAsyncFunc('APIRequest', async (subsegment) => {
555
+ // AWSXRay.setSegment(customSegment);
556
+ // try {
557
+ // // Your custom subsegments here
558
+ // subsegment.addAnnotation('host', this.host);
559
+ // subsegment.addAnnotation('method', this.method);
560
+
561
+ // const result = await this._performRequest();
562
+ // return result;
563
+ // } finally {
564
+ // AWSXRay.setSegment(parentSegment);
565
+ // }
566
+ // }, customSegment);
567
+ // }
568
+
569
+ const subsegmentName = "APIRequest/" + ((this.getHost()) ? this.getHost() : new URL(this.getURI()).hostname);
570
+ // const parentSegment = AWSXRay.getSegment();
571
+ // const customSegment = new AWSXRay.Segment('APIRequest');
572
+
573
+ await AWSXRay.captureAsyncFunc(subsegmentName, async (subsegment) => {
574
+ // AWSXRay.setSegment(subsegment);
575
+
576
+ try {
577
+
578
+ subsegment.namespace = 'remote';
579
+
580
+ // Add searchable annotations
581
+ subsegment.addAnnotation('request_method', this.getMethod());
582
+ subsegment.addAnnotation('request_host', this.getHost());
583
+ subsegment.addAnnotation('request_uri', this.getURI(false));
584
+ subsegment.addAnnotation('request_note', this.getNote());
585
+
586
+ const result = await _httpGetExecute(options, this, subsegment);
587
+
588
+ console.log("RESULT", result);
589
+
590
+ subsegment.addAnnotation('success', result ? "true" : "false");
591
+ subsegment.addAnnotation('status_code', this.#response?.statusCode || 500);
592
+ subsegment.addAnnotation('note', this.getNote());
593
+ return result;
594
+ } catch (error) {
595
+ DebugAndLog.error(`Error in APIRequest call to remote endpoint (${this.getNote()}): ${error.message}`, error.stack);
596
+ subsegment.addError(error);
597
+ throw error;
598
+ } finally {
599
+ subsegment.close();
600
+ // AWSXRay.setSegment(parentSegment);
601
+ }
602
+ }/*, customSegment*/);
603
+ } else {
604
+ await _httpGetExecute(options, this);
605
+ }
606
+ };
607
+
608
+ // we now have a completed response
609
+ resolve( this.#response );
610
+ }
611
+ catch (error) {
612
+ DebugAndLog.error(`Error in APIRequest call to _httpGetExecute (${this.getNote()}): ${error.message}`, error.stack);
613
+ reject(APIRequest.responseFormat(false, 500, "Error during send request"));
614
+ }
615
+ } catch (error) {
616
+ DebugAndLog.error(`API error while trying request for host ${this.getHost()} ${this.getNote()} ${error.message}`, { APIRequest: this.toObject(), trace: error.stack } );
617
+ reject(APIRequest.responseFormat(false, 500, "Error during send request"));
618
+
619
+ }
620
+ });
621
+
622
+
623
+ };
624
+
625
+ /**
626
+ * Get information about the ClientRequest and the Response including
627
+ * any redirects encountered, the request and response objects,
628
+ * whether or not the request was sent, and the max number of
629
+ * redirects allowed.
630
+ * @returns { {MAX_REDIRECTS: number, request: object, requestComplete: boolean, redirects: Array<string>, response: object}} Information about the request
631
+ */
632
+ toObject() {
633
+
634
+ return {
635
+ MAX_REDIRECTS: APIRequest.MAX_REDIRECTS,
636
+ request: this.#request,
637
+ requestComplete: this.#requestComplete,
638
+ redirects: this.#redirects,
639
+ response: this.#response
640
+ };
641
+
642
+ };
643
+
644
+ /**
645
+ * Formats the response for returning to program logic
646
+ * @param {boolean} success
647
+ * @param {number} statusCode
648
+ * @param {string} message
649
+ * @param {object} headers
650
+ * @param {string} body
651
+ * @returns {
652
+ * {
653
+ * success: boolean
654
+ * statusCode: number
655
+ * headers: object
656
+ * body: string
657
+ * message: string
658
+ * }
659
+ * }
660
+ */
661
+ static responseFormat(success = false, statusCode = 0, message = null, headers = null, body = null) {
662
+
663
+ return {
664
+ success: success,
665
+ statusCode: statusCode,
666
+ headers: headers,
667
+ body: body,
668
+ message: message
669
+ };
670
+ };
671
+ };
672
+
673
+ module.exports = APIRequest;