@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.
- package/CHANGELOG.md +234 -0
- package/LICENSE.txt +21 -0
- package/README.md +1265 -0
- package/SECURITY.md +5 -0
- package/package.json +58 -0
- package/src/index.js +9 -0
- package/src/lib/dao-cache.js +2024 -0
- package/src/lib/dao-endpoint.js +186 -0
- package/src/lib/tools/APIRequest.class.js +673 -0
- package/src/lib/tools/AWS.classes.js +250 -0
- package/src/lib/tools/CachedParametersSecrets.classes.js +492 -0
- package/src/lib/tools/ClientRequest.class.js +567 -0
- package/src/lib/tools/Connections.classes.js +466 -0
- package/src/lib/tools/DebugAndLog.class.js +416 -0
- package/src/lib/tools/ImmutableObject.class.js +71 -0
- package/src/lib/tools/RequestInfo.class.js +323 -0
- package/src/lib/tools/Response.class.js +547 -0
- package/src/lib/tools/ResponseDataModel.class.js +183 -0
- package/src/lib/tools/Timer.class.js +189 -0
- package/src/lib/tools/generic.response.html.js +88 -0
- package/src/lib/tools/generic.response.json.js +102 -0
- package/src/lib/tools/generic.response.rss.js +88 -0
- package/src/lib/tools/generic.response.text.js +86 -0
- package/src/lib/tools/generic.response.xml.js +82 -0
- package/src/lib/tools/index.js +318 -0
- package/src/lib/tools/utils.js +305 -0
- package/src/lib/tools/vars.js +34 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
const RequestInfo = require('./RequestInfo.class');
|
|
2
|
+
const Timer = require('./Timer.class');
|
|
3
|
+
const DebugAndLog = require('./DebugAndLog.class');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extends RequestInfo
|
|
8
|
+
* Can be used to create a custom ClientRequest object
|
|
9
|
+
*/
|
|
10
|
+
class ClientRequest extends RequestInfo {
|
|
11
|
+
|
|
12
|
+
static #validations = {
|
|
13
|
+
referrers: ['*'],
|
|
14
|
+
parameters: {}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
static #authenticationIsRequired = false; // is it a public API (no authentication required) or authenticated API? (if both, set to false and use authorizations and roles)
|
|
18
|
+
static #unauthenticatedAuthorizations = (ClientRequest.#authenticationIsRequired) ? ['none'] : ['all']; // change from 'all' if there is a mix of public and authenticated access
|
|
19
|
+
|
|
20
|
+
/* we would need to add valid roles and authorizations as well as static */
|
|
21
|
+
|
|
22
|
+
/* What and who of the request */
|
|
23
|
+
#event = null;
|
|
24
|
+
#context = null;
|
|
25
|
+
#authorizations = JSON.parse(JSON.stringify(ClientRequest.#unauthenticatedAuthorizations));
|
|
26
|
+
#roles = [];
|
|
27
|
+
|
|
28
|
+
/* The request data */
|
|
29
|
+
#props = {};
|
|
30
|
+
|
|
31
|
+
/* Logging */
|
|
32
|
+
#timer = null;
|
|
33
|
+
#logs = {
|
|
34
|
+
pathLog: [],
|
|
35
|
+
queryLog: [],
|
|
36
|
+
apiKey: null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initializes the request data based on the event. Also sets the
|
|
42
|
+
* validity of the request so it may be checked by .isValid()
|
|
43
|
+
* @param {object} event object from Lambda
|
|
44
|
+
*/
|
|
45
|
+
constructor(event, context) {
|
|
46
|
+
super(event);
|
|
47
|
+
|
|
48
|
+
this.#timer = new Timer("ClientRequest", true);
|
|
49
|
+
|
|
50
|
+
this.#event = event;
|
|
51
|
+
this.#context = context;
|
|
52
|
+
|
|
53
|
+
this.#authenticate();
|
|
54
|
+
|
|
55
|
+
const { resource, resourceArray, path, pathArray } = this.#extractResourceAndPath();
|
|
56
|
+
|
|
57
|
+
this.#props = {
|
|
58
|
+
method: this.#event.httpMethod,
|
|
59
|
+
path,
|
|
60
|
+
pathArray,
|
|
61
|
+
resource,
|
|
62
|
+
resourceArray,
|
|
63
|
+
pathParameters: {},
|
|
64
|
+
queryStringParameters: {},
|
|
65
|
+
headerParameters: {},
|
|
66
|
+
cookieParameters: {},
|
|
67
|
+
bodyPayload: this.#event?.body || null, // from body
|
|
68
|
+
client: {
|
|
69
|
+
isAuthenticated: this.isAuthenticated(),
|
|
70
|
+
isGuest: this.isGuest(),
|
|
71
|
+
authorizations: this.getAuthorizations(),
|
|
72
|
+
roles: this.getRoles()
|
|
73
|
+
},
|
|
74
|
+
deadline: (this.deadline() - 500),
|
|
75
|
+
calcMsToDeadline: this.calcMsToDeadline
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this.#validate();
|
|
79
|
+
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* This is used to initialize the ClientRequest class for all requests.
|
|
84
|
+
* Add ClientRequest.init(options) to the Config.init process or at the
|
|
85
|
+
* top of the main index.js file outside of the handler.
|
|
86
|
+
* @param {Array<string>} options.validations.referrers An array of accepted referrers. String matching goes from right to left, so ['example.com'] will allow example.com and subdomain.example.com
|
|
87
|
+
* @param {object} options.validations.parameters An object containing functions for validating request parameters (path, querystring, headers, cookies, etc).
|
|
88
|
+
*/
|
|
89
|
+
static init(options) {
|
|
90
|
+
if (typeof options === 'object') {
|
|
91
|
+
if ('validations' in options) {
|
|
92
|
+
if ('referrers' in options.validations) {
|
|
93
|
+
ClientRequest.#validations.referrers = options.validations.referrers;
|
|
94
|
+
}
|
|
95
|
+
if ('parameters' in options.validations) {
|
|
96
|
+
ClientRequest.#validations.parameters = options.validations.parameters;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
const errMsg = 'Application Configuration Error. Invalid options passed to ClientRequest.init(). Received:';
|
|
101
|
+
DebugAndLog.error(errMsg, options);
|
|
102
|
+
throw new Error(errMsg, options);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
static getReferrerWhiteList() {
|
|
108
|
+
return ClientRequest.#validations.referrers;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
static getParameterValidations() {
|
|
112
|
+
return ClientRequest.#validations.parameters;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Used in the constructor to set validity of the request
|
|
117
|
+
* This method may be customized to meet your validation needs
|
|
118
|
+
*/
|
|
119
|
+
#validate() {
|
|
120
|
+
|
|
121
|
+
let valid = false;
|
|
122
|
+
|
|
123
|
+
// add your additional validations here
|
|
124
|
+
valid = this.isAuthorizedReferrer() && this.#hasValidPathParameters() && this.#hasValidQueryStringParameters() && this.#hasValidHeaderParameters() && this.#hasValidCookieParameters();
|
|
125
|
+
|
|
126
|
+
// set the variable
|
|
127
|
+
super._isValid = valid;
|
|
128
|
+
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
#hasValidParameters(paramValidations, clientParameters) {
|
|
132
|
+
|
|
133
|
+
let rValue = {
|
|
134
|
+
isValid: true,
|
|
135
|
+
params: {}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (clientParameters && paramValidations) {
|
|
139
|
+
// Use a for...of loop instead of forEach for better control flow
|
|
140
|
+
for (const [key, value] of Object.entries(clientParameters)) {
|
|
141
|
+
const paramKey = key.replace(/^\/|\/$/g, '');
|
|
142
|
+
const paramValue = value;
|
|
143
|
+
|
|
144
|
+
if (paramKey in paramValidations) {
|
|
145
|
+
const validationFunc = paramValidations[paramKey];
|
|
146
|
+
if (typeof validationFunc === 'function' && validationFunc(paramValue)) {
|
|
147
|
+
rValue.params[paramKey] = paramValue;
|
|
148
|
+
} else {
|
|
149
|
+
DebugAndLog.warn(`Invalid parameter: ${paramKey} = ${paramValue}`);
|
|
150
|
+
rValue.isValid = false;
|
|
151
|
+
rValue.params = {};
|
|
152
|
+
return rValue; // exit right away
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return rValue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#hasValidPathParameters() {
|
|
161
|
+
const { isValid, params } = this.#hasValidParameters(ClientRequest.getParameterValidations()?.pathParameters, this.#event?.pathParameters);
|
|
162
|
+
this.#props.pathParameters = params;
|
|
163
|
+
return isValid;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#hasValidQueryStringParameters() {
|
|
167
|
+
// lowercase all the this.#event.queryStringParameters keys
|
|
168
|
+
const qs = {};
|
|
169
|
+
for (const key in this.#event.queryStringParameters) {
|
|
170
|
+
qs[key.toLowerCase()] = this.#event.queryStringParameters[key];
|
|
171
|
+
}
|
|
172
|
+
const { isValid, params } = this.#hasValidParameters(ClientRequest.getParameterValidations()?.queryStringParameters, qs);
|
|
173
|
+
this.#props.queryStringParameters = params;
|
|
174
|
+
return isValid;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#hasValidHeaderParameters() {
|
|
178
|
+
// camel case all the this.#event.headers keys and remove hyphens
|
|
179
|
+
const headers = {};
|
|
180
|
+
for (const key in this.#event.headers) {
|
|
181
|
+
const camelCaseKey = key.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
182
|
+
headers[camelCaseKey] = this.#event.headers[key];
|
|
183
|
+
}
|
|
184
|
+
const { isValid, params } = this.#hasValidParameters(ClientRequest.getParameterValidations()?.headerParameters, headers);
|
|
185
|
+
this.#props.headerParameters = params;
|
|
186
|
+
return isValid;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#hasValidCookieParameters() {
|
|
190
|
+
const { isValid, params } = this.#hasValidParameters(ClientRequest.getParameterValidations()?.cookiearameters, this.#event?.cookie); // TODO
|
|
191
|
+
this.#props.cookieParameters = params;
|
|
192
|
+
return isValid;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Utility function for getPathArray and getResourceArray
|
|
198
|
+
* @param {array<string>} arr array to slice
|
|
199
|
+
* @param {number} n number of elements to return
|
|
200
|
+
* @returns {array<string>} array of elements
|
|
201
|
+
*/
|
|
202
|
+
#getArray(arr, n = 0) {
|
|
203
|
+
if (n === 0 || arr.length <= n || (n < 0 && arr.length <= (n*-1))) {
|
|
204
|
+
return arr;
|
|
205
|
+
} else if (n > 0) {
|
|
206
|
+
return arr.slice(0, n);
|
|
207
|
+
} else {
|
|
208
|
+
// Handle negative indices by counting from the end
|
|
209
|
+
return arr.slice(n);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
#getElementAt(arr, n = 0) {
|
|
214
|
+
if (arr.length <= n || (n < 0 && arr.length <= (n*-1)-1)) return null;
|
|
215
|
+
if (n < 0) {
|
|
216
|
+
// Handle negative indices by counting from the end
|
|
217
|
+
return arr[arr.length + n];
|
|
218
|
+
} else {
|
|
219
|
+
return arr[n];
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get the first n path elements as a string.
|
|
225
|
+
* If n is 0, the whole path will be provided
|
|
226
|
+
* If n is a negative number, the last n elements will be provided
|
|
227
|
+
* The return value is a string with each element separated by a slash.
|
|
228
|
+
* @param {number} n number of elements to return.
|
|
229
|
+
* @returns {string} path elements
|
|
230
|
+
*/
|
|
231
|
+
getPath(n = 0) {
|
|
232
|
+
return this.getPathArray(n).join('/');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get the first n path elements as an array.
|
|
237
|
+
* If n is 0, the whole path will be provided
|
|
238
|
+
* If n is a negative number, the last n elements will be provided
|
|
239
|
+
* The return value is an array of strings.
|
|
240
|
+
* @param {number} n number of elements to return.
|
|
241
|
+
* @returns {array<string>} path elements
|
|
242
|
+
*/
|
|
243
|
+
getPathArray(n = 0) {
|
|
244
|
+
return this.#getArray(this.#props.pathArray, n);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get the path element at the specified index. If n is a negative number then return the nth element from the end.
|
|
250
|
+
* @param {number} n index of the resource to return
|
|
251
|
+
* @returns {string} path element
|
|
252
|
+
*/
|
|
253
|
+
getPathAt(n = 0) {
|
|
254
|
+
return this.#getElementAt(this.#props.pathArray, n);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get the first n resource elements as a string.
|
|
259
|
+
* If n is 0, the whole resource will be provided
|
|
260
|
+
* If n is a negative number, the last n elements will be provided
|
|
261
|
+
* The return value is a string with each element separated by a slash.
|
|
262
|
+
* @param {number} n number of elements to return.
|
|
263
|
+
* @returns {string} resource elements
|
|
264
|
+
*/
|
|
265
|
+
getResource(n = 0) {
|
|
266
|
+
return this.getResourceArray(n).join('/');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get the first n resource elements as an array.
|
|
271
|
+
* If n is 0, the whole resource will be provided
|
|
272
|
+
* If n is a negative number, the last n elements will be provided
|
|
273
|
+
* The return value is an array of strings.
|
|
274
|
+
* @param {number} n number of elements to return.
|
|
275
|
+
* @returns {array<string>} resource elements
|
|
276
|
+
*/
|
|
277
|
+
getResourceArray(n = 0) {
|
|
278
|
+
return this.#getArray(this.#props.resourceArray, n);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get the resource element at the specified index. If n is a negative number then return the nth element from the end.
|
|
283
|
+
* @param {number} n index of the resource to return
|
|
284
|
+
* @returns {string} resource element
|
|
285
|
+
*/
|
|
286
|
+
getResourceAt(n = 0) {
|
|
287
|
+
return this.#getElementAt(this.#props.resourceArray, n);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Returns the path parameters received in the request.
|
|
292
|
+
* Path parameters are defined in the API's path definition and validated in the applications validation functions.
|
|
293
|
+
* @returns {object} path parameters
|
|
294
|
+
*/
|
|
295
|
+
getPathParameters() {
|
|
296
|
+
return this.#props.pathParameters;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Returns the query string parameters received in the request.
|
|
301
|
+
* Query string parameters are validated in the applications validation functions.
|
|
302
|
+
* @returns {object} query string parameters
|
|
303
|
+
*/
|
|
304
|
+
getQueryStringParameters() {
|
|
305
|
+
return this.#props.queryStringParameters;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Returns the header parameters received in the request.
|
|
310
|
+
* Only headers validated in the applications validation functions are returned.
|
|
311
|
+
* @returns {object} header parameters
|
|
312
|
+
*/
|
|
313
|
+
getHeaderParameters() {
|
|
314
|
+
return this.#props.headerParameters;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Returns the cookie parameters received in the request.
|
|
319
|
+
* Only cookies validated in the applications validation functions are returned.
|
|
320
|
+
* @returns {object} cookie parameters
|
|
321
|
+
*/
|
|
322
|
+
getCookieParameters() {
|
|
323
|
+
return this.#props.cookieParameters;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
#authenticate() {
|
|
327
|
+
// add your authentication logic here
|
|
328
|
+
this.authenticated = false; // anonymous
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
isAuthenticated() {
|
|
332
|
+
return (ClientRequest.#authenticationIsRequired && this.authenticated);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
isGuest() {
|
|
336
|
+
return (!ClientRequest.#authenticationIsRequired && !this.authenticated);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
isAuthorizedToPerform(action="all") {
|
|
340
|
+
return ( this.getAuthorizations().includes(action) || this.getAuthorizations().includes('all'));
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
getRoles() {
|
|
344
|
+
if (this.isAuthenticated()) {
|
|
345
|
+
return this.#roles;
|
|
346
|
+
} else {
|
|
347
|
+
return ['guest'];
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
getAuthorizations() {
|
|
352
|
+
if (this.isAuthenticated()) {
|
|
353
|
+
return this.#authorizations;
|
|
354
|
+
} else {
|
|
355
|
+
return JSON.parse(JSON.stringify(ClientRequest.#unauthenticatedAuthorizations));
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
isAuthorizedReferrer() {
|
|
360
|
+
/* Check the array of valid referrers */
|
|
361
|
+
/* Check if the array includes a wildcard (*) OR if one of the whitelisted referrers matches the end of the referrer */
|
|
362
|
+
if (ClientRequest.requiresValidReferrer()) {
|
|
363
|
+
return true;
|
|
364
|
+
} else {
|
|
365
|
+
for (let i = 0; i < ClientRequest.#validations.referrers.length; i++) {
|
|
366
|
+
if (this.getClientReferer().endsWith(ClientRequest.#validations.referrers[i])) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
static requiresValidReferrer() {
|
|
375
|
+
return !ClientRequest.#validations.referrers.includes('*');
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
hasNoAuthorization() {
|
|
379
|
+
return (this.getAuthorizations().includes('none') || !this.isAuthorizedReferrer() );
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
getExecutionTime() {
|
|
384
|
+
return this.#timer.elapsed();
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
getFinalExecutionTime() {
|
|
388
|
+
return this.#timer.stop();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get the _processed_ request properties. These are the properties that
|
|
393
|
+
* the ClientRequest object took from the event sent to Lambda, validated,
|
|
394
|
+
* supplemented, and makes available to controllers.
|
|
395
|
+
* @returns {{ method: string, path: string, pathArray: string[], resource: string, resourceArray[], pathParameters: {}, queryStringParameters: {}, headerParameters: {}, cookieParameters: {}, bodyPayload: string, client: {isAuthenticated: boolean, isGuest: boolean, authorizations: string[], roles: string[]}, deadline: number, calcMsToDeadline: number}
|
|
396
|
+
*/
|
|
397
|
+
getProps() {
|
|
398
|
+
return this.#props;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Add one or more path notations to the log.
|
|
403
|
+
* These are used for logging and monitoring. When a response is finalized the route
|
|
404
|
+
* is recorded in CloudWatch logs along with the status and other information.
|
|
405
|
+
* Do not send sensitive information in the path notation, use placeholders instead.
|
|
406
|
+
* For example, /user/{id}/profile instead of /user/123/profile
|
|
407
|
+
* However, /city/Chicago is acceptable because it is not a sensitive identifier.
|
|
408
|
+
* Only add meaningful parameters. You can abbreviate and rewrite long parameters.
|
|
409
|
+
* For example, /format/jpg can be coded as /f:jpg or /user/123/profile/privacy as /userProfile/privacy
|
|
410
|
+
* @param {string|Array<string>} path
|
|
411
|
+
*/
|
|
412
|
+
addPathLog(path = null) {
|
|
413
|
+
if (path === null) {
|
|
414
|
+
path = `${this.#props.method}:${this.#props.pathArray.join("/")}`;
|
|
415
|
+
}
|
|
416
|
+
if (typeof path === 'string') {
|
|
417
|
+
this.#logs.pathLog.push(path);
|
|
418
|
+
} else if (Array.isArray(path)) {
|
|
419
|
+
this.#logs.pathLog = this.#logs.pathLog.concat(path);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Add one or more query notations to the query log.
|
|
425
|
+
* These are used for logging and monitoring. When a response is finalized the
|
|
426
|
+
* parameters are recorded in CloudWatch logs along with the status and other
|
|
427
|
+
* information.
|
|
428
|
+
* Do not send sensitive information in the query notation, use placeholders instead.
|
|
429
|
+
* For example, user instead of user=123
|
|
430
|
+
* However, city=Chicago is acceptable because it is not a sensitive query.
|
|
431
|
+
* Only add meaningful parameters. You can abbreviate long parameters.
|
|
432
|
+
* For example, format=jpg can be coded as f:jpg
|
|
433
|
+
* @param {string|Array<string>} query
|
|
434
|
+
*/
|
|
435
|
+
addQueryLog(query) {
|
|
436
|
+
if (typeof query === 'string') {
|
|
437
|
+
this.#logs.queryLog.push(query);
|
|
438
|
+
} else if (Array.isArray(query)) {
|
|
439
|
+
this.#logs.queryLog = this.#logs.queryLog.concat(query);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Get the request log entries
|
|
445
|
+
* resource: http method and resource path with path parameter keys (no values)
|
|
446
|
+
* queryKeys: query string keys (no values)
|
|
447
|
+
* pathLog: custom route path with values (set by application using addPathLog())
|
|
448
|
+
* queryLog: custom query with or without values (set by addQueryLog())
|
|
449
|
+
* apiKey: last 6 characters of api key if present
|
|
450
|
+
* @returns {resource: string, queryKeys: string, routeLog: string, queryLog: string, apiKey: string}
|
|
451
|
+
*/
|
|
452
|
+
getRequestLog() {
|
|
453
|
+
return {
|
|
454
|
+
resource: `${this.#props.method}:${this.#props.resourceArray.join('/')}`,
|
|
455
|
+
// put queryString keys in alpha order and join with &
|
|
456
|
+
queryKeys: Object.keys(this.#props.queryStringParameters).sort().map(key => `${key}=${this.#props.queryStringParameters[key]}`).join('&'),
|
|
457
|
+
routeLog: this.#logs.pathLog.join('/'),
|
|
458
|
+
// put logs.params in alpha order and join with &
|
|
459
|
+
queryLog: this.#logs.queryLog.sort().join('&'),
|
|
460
|
+
// only show last 6 characters of this.apiKey
|
|
461
|
+
apiKey: (this.#logs.apiKey !== null) ? this.#logs.apiKey.substring(this.#logs.apiKey.length - 6) : null
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
timerStop() {
|
|
466
|
+
return this.#timer?.stop() || 0;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
*
|
|
471
|
+
* @returns {number} The remaining time before Lambda times out. 1000 if context is not set in ClientRequest object.
|
|
472
|
+
*/
|
|
473
|
+
getRemainingTimeInMillis() {
|
|
474
|
+
return this.getContext().getRemainingTimeInMillis() || 1000;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get the number of milliseconds remaining and deduct the headroom given.
|
|
479
|
+
* Useful when you want to set a timeout on a function (such as an http request)
|
|
480
|
+
* that may take longer than our function has time for.
|
|
481
|
+
* @param {number} headroomInMillis number in milliseconds to deduct from Remaining Time
|
|
482
|
+
* @returns {number} greater than or equal to 0
|
|
483
|
+
*/
|
|
484
|
+
calcRemainingTimeInMillis(headroomInMillis = 0) {
|
|
485
|
+
let rt = this.getRemainingTimeInMillis() - headroomInMillis;
|
|
486
|
+
return (rt > 0 ? rt : 0);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
*
|
|
491
|
+
* @returns timestamp for when the remaining time is up
|
|
492
|
+
*/
|
|
493
|
+
deadline() {
|
|
494
|
+
return Date.now() + this.getRemainingTimeInMillis();
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
*
|
|
499
|
+
* @returns Milliseconds to Deadline
|
|
500
|
+
*/
|
|
501
|
+
calcMsToDeadline(deadline) {
|
|
502
|
+
if (!deadline) {
|
|
503
|
+
deadline = Date.now() - 500;
|
|
504
|
+
}
|
|
505
|
+
return deadline - Date.now();
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
getContext() {
|
|
509
|
+
if (this.#context === null) {
|
|
510
|
+
DebugAndLog.warn("Context for request is null but was requested. Set context along with event when constructing ClientRequest object");
|
|
511
|
+
}
|
|
512
|
+
return this.#context;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
getEvent() {
|
|
516
|
+
return this.#event;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
#extractResourceAndPath() {
|
|
520
|
+
const {resource, path} = this.getEvent();
|
|
521
|
+
|
|
522
|
+
let resourceIndex = [];
|
|
523
|
+
|
|
524
|
+
const resourcesAndPaths = {
|
|
525
|
+
resource: '',
|
|
526
|
+
resourceArray: [],
|
|
527
|
+
path: '',
|
|
528
|
+
pathArray: []
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
/* We want to use reqContext.resourcePath to create a resourcePath and resourceArray, and we want to use path to create a path and pathArray
|
|
532
|
+
For resourcePathArray, we want to split resourcePath on / and remove any empty strings. We also want to lowercase any element that is not surrounded with {}
|
|
533
|
+
We want to add the index of any resource element that is surrounded with {} to the resourceIndex array.
|
|
534
|
+
For pathArray we want to split on / and remove any empty strings. We also want to lowercase any element that is not at an index listed in the resourceIndex array
|
|
535
|
+
*/
|
|
536
|
+
if (resource) {
|
|
537
|
+
const resourceArray = resource.split('/').filter((element) => element !== '');
|
|
538
|
+
resourceArray.forEach((element, index) => {
|
|
539
|
+
if (element.startsWith('{') && element.endsWith('}')) {
|
|
540
|
+
resourceIndex.push(index);
|
|
541
|
+
resourcesAndPaths.resourceArray.push(element);
|
|
542
|
+
} else {
|
|
543
|
+
resourcesAndPaths.resourceArray.push(element.toLowerCase());
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
resourcesAndPaths.resource = resourcesAndPaths.resourceArray.join('/');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (path) {
|
|
550
|
+
const pathArray = path.split('/').filter((element) => element !== '');
|
|
551
|
+
pathArray.forEach((element, index) => {
|
|
552
|
+
if (!resourceIndex.includes(index)) {
|
|
553
|
+
resourcesAndPaths.pathArray.push(element.toLowerCase());
|
|
554
|
+
} else {
|
|
555
|
+
resourcesAndPaths.pathArray.push(element);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
resourcesAndPaths.path = resourcesAndPaths.pathArray.join('/');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return resourcesAndPaths;
|
|
562
|
+
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
module.exports = ClientRequest;
|