@spytecgps/lambda-utils 2.3.27 → 2.4.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/dist/index.js CHANGED
@@ -1,753 +1 @@
1
- 'use strict';
2
-
3
- var sdkLogger = require('@spytecgps/sdk-logger');
4
- var dayjs = require('dayjs');
5
- var timezone = require('dayjs/plugin/timezone');
6
- var utc = require('dayjs/plugin/utc');
7
- var Joi = require('joi');
8
- var qs = require('qs');
9
- var rawMiddy = require('@middy/core');
10
- var httpErrorHandler = require('@middy/http-error-handler');
11
- var httpResponseSerializer = require('@middy/http-response-serializer');
12
- var sqsJsonBodyParser = require('@middy/sqs-json-body-parser');
13
- var merge = require('deepmerge');
14
- var inputOutputLogger = require('@middy/input-output-logger');
15
- var clientLambda = require('@aws-sdk/client-lambda');
16
-
17
- function _interopNamespaceDefault(e) {
18
- var n = Object.create(null);
19
- if (e) {
20
- Object.keys(e).forEach(function (k) {
21
- if (k !== 'default') {
22
- var d = Object.getOwnPropertyDescriptor(e, k);
23
- Object.defineProperty(n, k, d.get ? d : {
24
- enumerable: true,
25
- get: function () { return e[k]; }
26
- });
27
- }
28
- });
29
- }
30
- n.default = e;
31
- return Object.freeze(n);
32
- }
33
-
34
- var Joi__namespace = /*#__PURE__*/_interopNamespaceDefault(Joi);
35
- var qs__namespace = /*#__PURE__*/_interopNamespaceDefault(qs);
36
-
37
- dayjs.extend(utc);
38
- dayjs.extend(timezone);
39
- const json = Joi__namespace.extend((joi) => {
40
- return {
41
- type: 'object',
42
- base: joi.object(),
43
- messages: {
44
- 'json.valid': 'must be valid JSON',
45
- },
46
- coerce(value) {
47
- try {
48
- return { value: JSON.parse(value) };
49
- }
50
- catch (err) {
51
- return null;
52
- }
53
- },
54
- validate(value, helpers) {
55
- if (!value) {
56
- return { value, errors: helpers.error('json.valid') };
57
- }
58
- return { value };
59
- },
60
- };
61
- });
62
- const urlEncoded = Joi__namespace.extend((joi) => {
63
- return {
64
- type: 'object',
65
- base: joi.object(),
66
- coerce(value) {
67
- return { value: qs__namespace.parse(value) };
68
- },
69
- };
70
- });
71
- const imeiSchema = Joi__namespace.string()
72
- .regex(/^\d{15,16}$/)
73
- .message('Invalid IMEI');
74
- const iccidSchema = Joi__namespace.string()
75
- .regex(/^[0-9A-Za-z]{18,22}$/)
76
- .message('Invalid ICCID');
77
- const SpytecJoi = Joi__namespace.extend((joi) => ({
78
- type: 'imei',
79
- messages: 'Invalid IMEI',
80
- base: joi.string().regex(/^\d{15,16}$/),
81
- }), (joi) => ({
82
- type: 'iccid',
83
- messages: 'Invalid ICCID',
84
- base: joi.string().regex(/^[0-9A-Za-z]{18,22}$/),
85
- }), (joi) => ({
86
- type: 'urlEncodedObject',
87
- base: joi.object(),
88
- coerce(value) {
89
- return { value: qs__namespace.parse(value) };
90
- },
91
- }), (joi) => ({
92
- type: 'jsonObject',
93
- base: joi.object(),
94
- coerce(value) {
95
- try {
96
- return { value: JSON.parse(value) };
97
- }
98
- catch (err) {
99
- return null;
100
- }
101
- },
102
- validate(value, helpers) {
103
- if (!value) {
104
- return { value, errors: helpers.error('json.valid') };
105
- }
106
- return { value };
107
- },
108
- }), (joi) => ({
109
- type: 'delimitedArray',
110
- base: joi.array().default([]),
111
- coerce: (value) => ({
112
- value: value.split ? value.split(',') : value,
113
- }),
114
- }), (joi) => ({
115
- type: 'queryStringParameters',
116
- messages: 'Missing query parameters',
117
- base: joi.object().required(),
118
- }), (joi) => ({
119
- type: 'date',
120
- base: joi.date(),
121
- prepare(value, helpers) {
122
- try {
123
- const dayjsDate = dayjs.tz(value, 'UTC');
124
- if (dayjsDate.isValid()) {
125
- return { value: dayjsDate.toDate() };
126
- }
127
- }
128
- catch (error) {
129
- return helpers.error('any.invalid');
130
- }
131
- },
132
- }), (joi) => ({
133
- type: 'jsonArray',
134
- base: joi.array(),
135
- coerce(value) {
136
- try {
137
- return { value: JSON.parse(value) };
138
- }
139
- catch (err) {
140
- return { value: null };
141
- }
142
- },
143
- validate(value, helpers) {
144
- if (!Array.isArray(value)) {
145
- return { value, errors: helpers.error('jsonArray.schema') };
146
- }
147
- return { value };
148
- },
149
- }), (joi) => ({
150
- type: 'base64ThenUriEncodedObject',
151
- base: joi.object(),
152
- coerce(value) {
153
- try {
154
- const decodedValue = decodeURIComponent(Buffer.from(value, 'base64').toString());
155
- return { value: JSON.parse(decodedValue) };
156
- }
157
- catch (err) {
158
- return null;
159
- }
160
- },
161
- validate(value, helpers) {
162
- if (!value) {
163
- return { value, errors: helpers.error('json.valid') };
164
- }
165
- return { value };
166
- },
167
- }));
168
-
169
- const getAuthorizerValidator = (params = {}) => {
170
- return Joi__namespace.object({
171
- clientId: Joi__namespace.number().greater(0).required(),
172
- userId: Joi__namespace.string().guid( /*{ version: 'uuidv4' }*/).required(),
173
- resources: json.object({}),
174
- scope: Joi__namespace.string().optional(),
175
- // .error(() => new UnauthorizedError(`missing scope ${scope}`))
176
- type: Joi__namespace.string().optional(),
177
- // .error(() => new UnauthorizedError(`missing user type ${type}`))
178
- enterprise: Joi__namespace.boolean().default(false),
179
- maintenanceModule: Joi__namespace.boolean().default(false),
180
- billingMethod: Joi__namespace.string().optional(),
181
- customerSegment: Joi__namespace.string().optional(),
182
- securityGroupTagId: Joi__namespace.number().optional().allow(null),
183
- securityRole: Joi__namespace.string().optional().allow(null),
184
- ...params,
185
- });
186
- };
187
- const getAuthorizerValidatorV4 = (params = {}) => {
188
- return Joi__namespace.object({
189
- clientId: Joi__namespace.number().greater(0).required(),
190
- userId: Joi__namespace.string().guid( /*{ version: 'uuidv4' }*/).required(),
191
- scope: Joi__namespace.string().optional(),
192
- type: Joi__namespace.string().optional(),
193
- ...params,
194
- });
195
- };
196
- /**
197
- * @deprecated
198
- */
199
- const requestContextValidator = Joi__namespace.object({
200
- authorizer: getAuthorizerValidator(),
201
- });
202
- const getRequestContextValidator = (params = {}) => {
203
- return Joi__namespace.object({
204
- authorizer: getAuthorizerValidator(params),
205
- });
206
- };
207
- const getRequestContextValidatorV4 = (params = {}) => {
208
- return Joi__namespace.object({
209
- authorizer: getAuthorizerValidatorV4(params),
210
- });
211
- };
212
-
213
- class HttpError extends Error {
214
- }
215
-
216
- class BadRequestError extends HttpError {
217
- code = 400;
218
- statusCode = 400;
219
- name = 'BadRequestError';
220
- }
221
-
222
- class BaseError extends Error {
223
- code;
224
- statusCode;
225
- }
226
-
227
- class ConflictError extends HttpError {
228
- code = 409;
229
- statusCode = 409;
230
- name = 'ConflictError';
231
- }
232
-
233
- class ForbiddenError extends HttpError {
234
- code = 403;
235
- statusCode = 403;
236
- name = 'ForbiddenError';
237
- }
238
-
239
- class NotFoundError extends HttpError {
240
- code = 404;
241
- statusCode = 404;
242
- name = 'NotFoundError';
243
- }
244
-
245
- class UnauthorizedError extends HttpError {
246
- code = 401;
247
- statusCode = 401;
248
- name = 'UnauthorizedError';
249
- }
250
-
251
- const validateEvent = (event, schema, validateOptions) => {
252
- if (!schema) {
253
- sdkLogger.logger.warn(`skipping validation`);
254
- return event;
255
- }
256
- const { error, value } = schema.validate(event, {
257
- allowUnknown: validateOptions?.allowUnknown || true,
258
- errors: {
259
- label: 'key',
260
- wrap: {
261
- label: false,
262
- },
263
- },
264
- });
265
- if (error) {
266
- sdkLogger.logger.error({ error }, 'Validation error');
267
- throw error.isJoi ? new BadRequestError(error.message) : error;
268
- }
269
- return value;
270
- };
271
-
272
- const defaultApiSchema = SpytecJoi.object({
273
- requestContext: getRequestContextValidator(),
274
- });
275
-
276
- const baseHeaders = {
277
- 'Content-Type': 'application/json',
278
- 'Access-Control-Allow-Origin': '*',
279
- 'Access-Control-Allow-Credentials': true,
280
- };
281
- const buildResponseBody = (statusCode, message, data) => {
282
- return {
283
- success: statusCode < 400,
284
- message,
285
- result: typeof data !== 'undefined' ? data : undefined,
286
- };
287
- };
288
- const buildProxyResult = ({ statusCode = 200, message = 'ok', data, headers = {}, multiValueHeaders = {}, rawResult = false, stringifyBody = true, }) => {
289
- const resp = rawResult ? data : buildResponseBody(statusCode, message, data);
290
- const body = stringifyBody ? resp && JSON.stringify(resp) : data;
291
- return {
292
- headers: { ...baseHeaders, ...headers },
293
- multiValueHeaders,
294
- statusCode,
295
- body,
296
- };
297
- };
298
-
299
- const logEvent$2 = (e) => ({
300
- resource: e.resource,
301
- httpMethod: e.httpMethod,
302
- queryStringParameters: e.queryStringParameters,
303
- pathParameters: e.pathParameters,
304
- body: e.body,
305
- });
306
- const apiGatewayEventWrapper = async ({ event, context, schema, handler, }) => {
307
- if (event && context) {
308
- sdkLogger.withRequest(event, context);
309
- }
310
- try {
311
- const validatedEvent = validateEvent(event, schema);
312
- const result = await handler(validatedEvent);
313
- return buildProxyResult(result);
314
- }
315
- catch (err) {
316
- sdkLogger.logger.error({ err, event: logEvent$2(event) }, `apiGatewayWrapper - caught error`);
317
- return buildProxyResult({
318
- statusCode: err.code || 500,
319
- message: err.message || 'Error',
320
- });
321
- }
322
- };
323
-
324
- const logEvent$1 = (e) => (e.Records || []).map((record) => ({
325
- messageId: record.messageId,
326
- body: record.body,
327
- messageAttributes: record.messageAttributes,
328
- }));
329
- async function processEvent$1(validatedEvent, handler, singleHandler, mode) {
330
- if (!(handler || singleHandler)) {
331
- throw new Error(`handler or singleHandler not defined`);
332
- }
333
- if (handler) {
334
- await handler(validatedEvent);
335
- }
336
- else if (singleHandler) {
337
- const records = validatedEvent.Records;
338
- if (mode === 'serial') {
339
- for (const record of records) {
340
- await singleHandler(record);
341
- }
342
- }
343
- else if (mode === 'parallel') {
344
- await Promise.all(records.map((record) => singleHandler(record)));
345
- }
346
- }
347
- }
348
- const sqsEventWrapper$1 = async ({ event, context, schema, handler, singleHandler, mode = 'serial', }) => {
349
- if (event && context) {
350
- sdkLogger.withRequest(event, context);
351
- }
352
- try {
353
- const validatedEvent = validateEvent(event, schema);
354
- await processEvent$1(validatedEvent, handler, singleHandler, mode);
355
- }
356
- catch (err) {
357
- sdkLogger.logger.error({ err, event: logEvent$1(event) }, `sqsEventWrapper - caught error`);
358
- throw err;
359
- }
360
- };
361
-
362
- const logEvent = (e) => (e.Records || []).map((record) => ({
363
- messageId: record.messageId,
364
- body: record.body,
365
- messageAttributes: record.messageAttributes,
366
- }));
367
- const processEvent = async (validatedEvent, handler, singleHandler, mode) => {
368
- if (!(handler || singleHandler)) {
369
- throw new Error(`handler or singleHandler not defined`);
370
- }
371
- if (handler) {
372
- return await handler(validatedEvent);
373
- }
374
- else if (singleHandler) {
375
- const records = validatedEvent.Records;
376
- if (mode === 'serial') {
377
- const result = [];
378
- for (const record of records) {
379
- const singleHandleResult = await singleHandler(record);
380
- result.push(singleHandleResult);
381
- }
382
- return result;
383
- }
384
- else if (mode === 'parallel') {
385
- return await Promise.all(records.map((record) => singleHandler(record)));
386
- }
387
- }
388
- };
389
- const sqsEventWrapper = async ({ event, context, schema, handler, singleHandler, mode = 'serial', }) => {
390
- if (event && context) {
391
- sdkLogger.withRequest(event, context);
392
- }
393
- try {
394
- const validatedEvent = validateEvent(event, schema);
395
- return await processEvent(validatedEvent, handler, singleHandler, mode);
396
- }
397
- catch (err) {
398
- sdkLogger.logger.error({ err, event: logEvent(event) }, `sqsEventWrapper - caught error`);
399
- throw err;
400
- }
401
- };
402
-
403
- const getEnvKey = () => {
404
- if (process.env.NODE_ENV === 'test') {
405
- return 'test';
406
- }
407
- if (process.env.IS_OFFLINE && process.env.STAGE === 'dev') {
408
- return 'local';
409
- }
410
- return (process.env.STAGE ?? process.env.NODE_ENV ?? 'dev');
411
- };
412
- const isLocal = () => getEnvKey() === 'local';
413
- const isTest = () => getEnvKey() === 'test';
414
- const isDev = () => getEnvKey() === 'dev';
415
- const isProduction = () => getEnvKey() === 'prod';
416
- const setupEnvConfig = (envConfigs) => {
417
- const baseConfig = envConfigs['base'];
418
- const envConfig = envConfigs[getEnvKey()] ?? {};
419
- return merge(baseConfig, envConfig);
420
- };
421
-
422
- const AMAZON_TRACE_ID = '_X_AMZN_TRACE_ID';
423
- const CORRELATION_HEADER = 'x-correlation-';
424
- const CORRELATION_ID = `${CORRELATION_HEADER}id`;
425
- const CORRELATION_TRACE_ID = `${CORRELATION_HEADER}trace-id`;
426
- const contextualLogger = () => {
427
- const before = async ({ event, context }) => {
428
- const ctx = {
429
- awsRequestId: context?.awsRequestId,
430
- };
431
- // capture api gateway request ID
432
- const apiRequestId = event?.requestContext?.requestId;
433
- if (apiRequestId) {
434
- ctx.apiRequestId = apiRequestId;
435
- }
436
- // capture any correlation headers sent from upstream callers
437
- if (event.headers) {
438
- Object.keys(event.headers).forEach((header) => {
439
- if (header.toLowerCase().startsWith(CORRELATION_HEADER)) {
440
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
441
- ctx[header] = event.headers[header];
442
- }
443
- });
444
- }
445
- // capture the xray trace id if its enabled
446
- if (process.env[AMAZON_TRACE_ID]) {
447
- ctx[CORRELATION_TRACE_ID] = process.env[AMAZON_TRACE_ID];
448
- }
449
- // set the correlation id if not already set by upstream callers
450
- /* istanbul ignore next */
451
- if (!ctx[CORRELATION_ID]) {
452
- ctx[CORRELATION_ID] = context.awsRequestId;
453
- }
454
- sdkLogger.logger.setHapnContext(ctx);
455
- };
456
- return {
457
- before,
458
- };
459
- };
460
- const contextualLoggerMiddleware = contextualLogger();
461
-
462
- const ioLoggerMiddleware = inputOutputLogger({
463
- omitPaths: [
464
- 'event.multiValueHeaders',
465
- 'event.multiValueQueryStringParameters',
466
- 'event.resource',
467
- 'event.httpMethod',
468
- 'event.headers',
469
- 'event.stageVariables',
470
- 'event.requestContext.resourceId',
471
- 'event.requestContext.resourcePath',
472
- 'event.requestContext.httpMethod',
473
- 'event.requestContext.extendedRequestId',
474
- 'event.requestContext.requestTime',
475
- 'event.requestContext.path',
476
- 'event.requestContext.accountId',
477
- 'event.requestContext.protocol',
478
- 'event.requestContext.stage',
479
- 'event.requestContext.domainPrefix',
480
- 'event.requestContext.requestTimeEpoch',
481
- 'event.requestContext.apiId',
482
- 'event.requestContext.domainName',
483
- 'event.requestContext.identity',
484
- 'event.isBase64Encoded',
485
- 'event.body',
486
- 'response.body',
487
- 'response.headers',
488
- ],
489
- logger: (req) => {
490
- const message = req?.event ? 'event' : 'response';
491
- sdkLogger.logger.info(req.event ?? req.response, message);
492
- },
493
- });
494
-
495
- const normalizerMiddleware = () => {
496
- return {
497
- before: async (request) => {
498
- const event = request.event;
499
- event.queryStringParameters = event.queryStringParameters || {};
500
- event.pathParameters = event.pathParameters || {};
501
- },
502
- };
503
- };
504
-
505
- const offlineAuthMiddleware = ({ authFunctionName = 'spytec-web-api-auth-prod-AuthorizerFunction', enabled = !!process.env.IS_OFFLINE, } = {}) => {
506
- const lambdaClient = new clientLambda.LambdaClient({ region: process.env.AWS_REGION });
507
- return {
508
- before: async (request) => {
509
- if (!enabled)
510
- return;
511
- const { event } = request;
512
- // Extract Bearer token from the Authorization header
513
- const authHeader = event.headers?.Authorization || event.headers?.authorization;
514
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
515
- // throw new Error('Authorization header is missing or invalid')
516
- // if we can't extract the token, this is is a public route, ignore it
517
- sdkLogger.logger.warn('Authorization header is missing or invalid, skipping fake offline authorization');
518
- return;
519
- }
520
- const token = authHeader.slice(7); // Remove 'Bearer ' prefix
521
- // Prepare payload for the auth function
522
- const payload = {
523
- authorizationToken: token,
524
- };
525
- // Invoke the auth function manually
526
- const command = new clientLambda.InvokeCommand({
527
- FunctionName: authFunctionName,
528
- Payload: Buffer.from(JSON.stringify(payload)),
529
- });
530
- try {
531
- const response = await lambdaClient.send(command);
532
- const responsePayload = JSON.parse(Buffer.from(response.Payload).toString());
533
- if (responsePayload.errorMessage) {
534
- throw new Error(responsePayload.errorMessage);
535
- }
536
- event.requestContext.authorizer = responsePayload.context;
537
- }
538
- catch (error) {
539
- sdkLogger.logger.error('Error invoking auth function:', error);
540
- throw new Error('Authorization failed');
541
- }
542
- },
543
- };
544
- };
545
-
546
- const responseWrapperMiddleware = () => {
547
- const responseWrapperMiddlewareAfter = (req) => {
548
- req.response = buildProxyResult(req.response);
549
- };
550
- const responseWrapperMiddlewareError = (req) => {
551
- const statusCode = req.error?.code ?? 500;
552
- const errorMessage = req.error?.message || 'Error';
553
- const loggerMethod = statusCode >= 500 ? 'error' : 'info';
554
- sdkLogger.logger[loggerMethod](req.error, 'Request failed');
555
- req.response = buildProxyResult({
556
- statusCode,
557
- message: errorMessage,
558
- });
559
- };
560
- return {
561
- after: responseWrapperMiddlewareAfter,
562
- onError: responseWrapperMiddlewareError,
563
- };
564
- };
565
-
566
- const validatorMiddleware = ({ schema, allowUnknown = true }) => {
567
- const validatorMiddlewareBefore = (request) => {
568
- const { error, value } = schema.validate(request.event, {
569
- allowUnknown,
570
- errors: {
571
- label: 'key',
572
- wrap: {
573
- label: false,
574
- },
575
- },
576
- });
577
- if (error) {
578
- sdkLogger.logger.error({ error }, 'Validation error');
579
- throw error.isJoi ? new BadRequestError(error.message) : error;
580
- }
581
- request.event = value;
582
- };
583
- return {
584
- before: validatorMiddlewareBefore,
585
- };
586
- };
587
-
588
- /* eslint-disable @typescript-eslint/no-explicit-any */
589
- const defaults = {
590
- isWarmingUp: (event) => event.source === 'serverless-plugin-warmup',
591
- };
592
- const warmupMiddleware = (opt = {}) => {
593
- const options = { ...defaults, ...opt };
594
- const warmupMiddlewareBefore = (request) => {
595
- if (options.isWarmingUp(request.event)) {
596
- return 'warmup';
597
- }
598
- };
599
- return {
600
- before: warmupMiddlewareBefore,
601
- };
602
- };
603
-
604
- const baseMiddlewares = [
605
- warmupMiddleware(),
606
- contextualLoggerMiddleware,
607
- !(isLocal() || isTest()) ? ioLoggerMiddleware : undefined,
608
- ].filter(Boolean);
609
- const apiGatewayMiddlewares = [
610
- httpResponseSerializer({
611
- serializers: [
612
- {
613
- regex: /^application\/xml$/,
614
- serializer: ({ body }) => `<message>${body}</message>`,
615
- },
616
- {
617
- regex: /^application\/json$/,
618
- serializer: ({ body }) => JSON.stringify(body),
619
- },
620
- {
621
- regex: /^text\/plain$/,
622
- serializer: ({ body }) => body,
623
- },
624
- ],
625
- default: 'application/json',
626
- }),
627
- responseWrapperMiddleware(),
628
- offlineAuthMiddleware(),
629
- normalizerMiddleware(),
630
- ];
631
- const middy = (handler) => rawMiddy(handler).use([...baseMiddlewares]);
632
- const apiGatewayMiddy = (handler) => rawMiddy(handler).use([...baseMiddlewares, ...apiGatewayMiddlewares]);
633
-
634
- class LambdaCache {
635
- collectionName;
636
- /**
637
- * @param {String} collectionName (not required) - The collection key used to store the cache values.
638
- * If not provide default collection name uses :
639
- * ${process.env.AWS_LAMBDA_FUNCTION_NAME}-${process.env.AWS_LAMBDA_FUNCTION_VERSION}
640
- * */
641
- constructor(collectionName) {
642
- this.collectionName =
643
- collectionName ?? `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${process.env.AWS_LAMBDA_FUNCTION_VERSION}`;
644
- if (!global['CACHE_STORAGE']) {
645
- global['CACHE_STORAGE'] = {};
646
- }
647
- if (!global['CACHE_STORAGE'][this.collectionName]) {
648
- global['CACHE_STORAGE'][this.collectionName] = new Map();
649
- }
650
- }
651
- /**
652
- * @param {String} key (required) - cache key
653
- * @param {Object} value (required) - cache value
654
- * @param {Number} expire (required) - cache expiration time (seconds)
655
- * */
656
- set(key, value, ttl) {
657
- const expire = 1000 * ttl + Date.now();
658
- global['CACHE_STORAGE'][this.collectionName].set(key, { value, expire });
659
- }
660
- /**
661
- * @param {String} key (required) - cache key to get
662
- * */
663
- get(key) {
664
- if (!key) {
665
- throw new Error('key is required!');
666
- }
667
- const record = global['CACHE_STORAGE'][this.collectionName].get(key);
668
- if (!record) {
669
- return null;
670
- }
671
- if (!record.expire || record.expire > Date.now()) {
672
- return record.value;
673
- }
674
- else {
675
- return this.remove(key);
676
- }
677
- }
678
- /**
679
- * @param {String} key (required) - cache key to remove
680
- * */
681
- remove(key) {
682
- const record = global['CACHE_STORAGE'][this.collectionName].get(key);
683
- if (!record) {
684
- return;
685
- }
686
- global['CACHE_STORAGE'][this.collectionName].delete(key);
687
- }
688
- }
689
-
690
- const promiseWithCache = async (promise, cacheInstance, cacheKey, ttl) => {
691
- let instance = cacheInstance.get(cacheKey);
692
- if (!instance) {
693
- instance = await promise();
694
- cacheInstance.set(cacheKey, instance, ttl);
695
- }
696
- return instance;
697
- };
698
-
699
- const promiseWithTimeout = (promise, ms, timeoutError = new Error('Promise timed out')) => {
700
- // create a promise that rejects in milliseconds
701
- const timeout = new Promise((_, reject) => {
702
- setTimeout(() => {
703
- reject(timeoutError);
704
- }, ms);
705
- });
706
- // returns a race between timeout and the passed promise
707
- return Promise.race([promise, timeout]);
708
- //Adding comment to test
709
- };
710
-
711
- exports.httpErrorHandler = httpErrorHandler;
712
- exports.httpResponseSerializer = httpResponseSerializer;
713
- exports.sqsJsonBodyParser = sqsJsonBodyParser;
714
- exports.merge = merge;
715
- exports.BadRequestError = BadRequestError;
716
- exports.BaseError = BaseError;
717
- exports.ConflictError = ConflictError;
718
- exports.ForbiddenError = ForbiddenError;
719
- exports.HttpError = HttpError;
720
- exports.LambdaCache = LambdaCache;
721
- exports.NotFoundError = NotFoundError;
722
- exports.SpytecJoi = SpytecJoi;
723
- exports.UnauthorizedError = UnauthorizedError;
724
- exports.apiGatewayEventWrapper = apiGatewayEventWrapper;
725
- exports.apiGatewayMiddlewares = apiGatewayMiddlewares;
726
- exports.apiGatewayMiddy = apiGatewayMiddy;
727
- exports.baseMiddlewares = baseMiddlewares;
728
- exports.buildProxyResult = buildProxyResult;
729
- exports.buildResponseBody = buildResponseBody;
730
- exports.defaultApiSchema = defaultApiSchema;
731
- exports.getAuthorizerValidator = getAuthorizerValidator;
732
- exports.getAuthorizerValidatorV4 = getAuthorizerValidatorV4;
733
- exports.getEnvKey = getEnvKey;
734
- exports.getRequestContextValidator = getRequestContextValidator;
735
- exports.getRequestContextValidatorV4 = getRequestContextValidatorV4;
736
- exports.iccidSchema = iccidSchema;
737
- exports.imeiSchema = imeiSchema;
738
- exports.isDev = isDev;
739
- exports.isLocal = isLocal;
740
- exports.isProduction = isProduction;
741
- exports.isTest = isTest;
742
- exports.json = json;
743
- exports.middy = middy;
744
- exports.promiseWithCache = promiseWithCache;
745
- exports.promiseWithTimeout = promiseWithTimeout;
746
- exports.requestContextValidator = requestContextValidator;
747
- exports.setupEnvConfig = setupEnvConfig;
748
- exports.sqsEventWrapper = sqsEventWrapper$1;
749
- exports.sqsEventWrapperWithReturn = sqsEventWrapper;
750
- exports.urlEncoded = urlEncoded;
751
- exports.validateEvent = validateEvent;
752
- exports.validatorMiddleware = validatorMiddleware;
753
- exports.warmupMiddleware = warmupMiddleware;
1
+ import{logger as e,withRequest as r}from"@spytecgps/sdk-logger";import t from"dayjs";import s from"dayjs/plugin/timezone";import o from"dayjs/plugin/utc";import*as a from"joi";import*as n from"qs";import i from"@middy/core";export{default as httpErrorHandler}from"@middy/http-error-handler";import l from"@middy/http-response-serializer";export{default as httpResponseSerializer}from"@middy/http-response-serializer";export{default as sqsJsonBodyParser}from"@middy/sqs-json-body-parser";import c from"deepmerge";export{default as merge}from"deepmerge";import d from"@middy/input-output-logger";import{LambdaClient as u,InvokeCommand as p}from"@aws-sdk/client-lambda";t.extend(o),t.extend(s);const m=a.extend((e=>({type:"object",base:e.object(),messages:{"json.valid":"must be valid JSON"},coerce(e){try{return{value:JSON.parse(e)}}catch(e){return null}},validate:(e,r)=>e?{value:e}:{value:e,errors:r.error("json.valid")}}))),v=a.extend((e=>({type:"object",base:e.object(),coerce:e=>({value:n.parse(e)})}))),g=a.string().regex(/^\d{15,16}$/).message("Invalid IMEI"),h=a.string().regex(/^[0-9A-Za-z]{18,22}$/).message("Invalid ICCID"),y=a.extend((e=>({type:"imei",messages:"Invalid IMEI",base:e.string().regex(/^\d{15,16}$/)})),(e=>({type:"iccid",messages:"Invalid ICCID",base:e.string().regex(/^[0-9A-Za-z]{18,22}$/)})),(e=>({type:"urlEncodedObject",base:e.object(),coerce:e=>({value:n.parse(e)})})),(e=>({type:"jsonObject",base:e.object(),coerce(e){try{return{value:JSON.parse(e)}}catch(e){return null}},validate:(e,r)=>e?{value:e}:{value:e,errors:r.error("json.valid")}})),(e=>({type:"delimitedArray",base:e.array().default([]),coerce:e=>({value:e.split?e.split(","):e})})),(e=>({type:"queryStringParameters",messages:"Missing query parameters",base:e.object().required()})),(e=>({type:"date",base:e.date(),prepare(e,r){try{const r=t.tz(e,"UTC");if(r.isValid())return{value:r.toDate()}}catch(e){return r.error("any.invalid")}}})),(e=>({type:"jsonArray",base:e.array(),coerce(e){try{return{value:JSON.parse(e)}}catch(e){return{value:null}}},validate:(e,r)=>Array.isArray(e)?{value:e}:{value:e,errors:r.error("jsonArray.schema")}})),(e=>({type:"base64ThenUriEncodedObject",base:e.object(),coerce(e){try{const r=decodeURIComponent(Buffer.from(e,"base64").toString());return{value:JSON.parse(r)}}catch(e){return null}},validate:(e,r)=>e?{value:e}:{value:e,errors:r.error("json.valid")}})));class b extends Error{}class f extends b{code=400;statusCode=400;name="BadRequestError"}class w extends Error{code;statusCode}class C extends b{code=409;statusCode=409;name="ConflictError"}class E extends b{code=403;statusCode=403;name="ForbiddenError"}class x extends b{code=404;statusCode=404;name="NotFoundError"}class A extends b{code=401;statusCode=401;name="UnauthorizedError"}const q=({scope:e,type:r}={})=>a.object({clientId:a.number(),resources:m.object({}),scope:e?a.string().pattern(new RegExp(`${e}`)).error((()=>new A(`missing scope ${e}`))):a.optional(),type:r?a.any().valid(r).error((()=>new A(`missing user type ${r}`))):a.optional(),enterprise:a.boolean().default(!1),maintenanceModule:a.boolean().default(!1),billingMethod:a.string().optional(),customerSegment:a.string().optional(),securityGroupTagId:a.number().optional().allow(null),securityRole:a.string().optional().allow(null)}),I=a.object({authorizer:q()}),N=(e={})=>a.object({authorizer:q(e)}),S=(r,t,s)=>{if(!t)return e.warn("skipping validation"),r;const{error:o,value:a}=t.validate(r,{allowUnknown:s?.allowUnknown||!0,errors:{label:"key",wrap:{label:!1}}});if(o)throw e.error({error:o},"Validation error"),o.isJoi?new f(o.message):o;return a},j=y.object({requestContext:N()}),O={"Content-Type":"application/json","Access-Control-Allow-Origin":"*","Access-Control-Allow-Credentials":!0},R=(e,r,t)=>({success:e<400,message:r,result:t||void 0}),_=({statusCode:e=200,message:r="ok",data:t,headers:s={},rawResult:o=!1,stringifyBody:a=!0})=>{const n=o?t:R(e,r,t),i=a?n&&JSON.stringify(n):t;return{headers:{...O,...s},statusCode:e,body:i}},z=async({event:t,context:s,schema:o,handler:a})=>{t&&s&&r(t,s);try{const e=S(t,o),r=await a(e);return _(r)}catch(r){return e.error({err:r,event:(n=t,{resource:n.resource,httpMethod:n.httpMethod,queryStringParameters:n.queryStringParameters,pathParameters:n.pathParameters,body:n.body})},"apiGatewayWrapper - caught error"),_({statusCode:r.code||500,message:r.message||"Error"})}var n};const T=async({event:t,context:s,schema:o,handler:a,singleHandler:n,mode:i="serial"})=>{t&&s&&r(t,s);try{const e=S(t,o);await async function(e,r,t,s){if(!r&&!t)throw new Error("handler or singleHandler not defined");if(r)await r(e);else if(t){const r=e.Records;if("serial"===s)for(const e of r)await t(e);else"parallel"===s&&await Promise.all(r.map((e=>t(e))))}}(e,a,n,i)}catch(r){throw e.error({err:r,event:(l=t,(l.Records||[]).map((e=>({messageId:e.messageId,body:e.body,messageAttributes:e.messageAttributes}))))},"sqsEventWrapper - caught error"),r}var l},P=async({event:t,context:s,schema:o,handler:a,singleHandler:n,mode:i="serial"})=>{t&&s&&r(t,s);try{const e=S(t,o);return await(async(e,r,t,s)=>{if(!r&&!t)throw new Error("handler or singleHandler not defined");if(r)return await r(e);if(t){const r=e.Records;if("serial"===s){const e=[];for(const s of r){const r=await t(s);e.push(r)}return e}if("parallel"===s)return await Promise.all(r.map((e=>t(e))))}})(e,a,n,i)}catch(r){throw e.error({err:r,event:(l=t,(l.Records||[]).map((e=>({messageId:e.messageId,body:e.body,messageAttributes:e.messageAttributes}))))},"sqsEventWrapper - caught error"),r}var l},M=()=>"test"===process.env.NODE_ENV?"test":process.env.IS_OFFLINE&&"dev"===process.env.STAGE?"local":process.env.STAGE??process.env.NODE_ENV??"dev",H=()=>"local"===M(),$=()=>"test"===M(),k=()=>"dev"===M(),G=()=>"prod"===M(),W=e=>{const r=e.base,t=e[M()]??{};return c(r,t)},J="_X_AMZN_TRACE_ID",U="x-correlation-",B=`${U}id`,F=`${U}trace-id`,D={before:async({event:r,context:t})=>{const s={awsRequestId:t?.awsRequestId},o=r?.requestContext?.requestId;o&&(s.apiRequestId=o),r.headers&&Object.keys(r.headers).forEach((e=>{e.toLowerCase().startsWith(U)&&(s[e]=r.headers[e])})),process.env[J]&&(s[F]=process.env[J]),s[B]||(s[B]=t.awsRequestId),e.setHapnContext(s)}},V=d({omitPaths:["event.multiValueHeaders","event.multiValueQueryStringParameters","event.resource","event.httpMethod","event.headers","event.stageVariables","event.requestContext.resourceId","event.requestContext.resourcePath","event.requestContext.httpMethod","event.requestContext.extendedRequestId","event.requestContext.requestTime","event.requestContext.path","event.requestContext.accountId","event.requestContext.protocol","event.requestContext.stage","event.requestContext.domainPrefix","event.requestContext.requestTimeEpoch","event.requestContext.apiId","event.requestContext.domainName","event.requestContext.identity","event.isBase64Encoded","event.body","response.body","response.headers"],logger:r=>{const t=r?.event?"event":"response";e.info(r.event??r.response,t)}}),L=({schema:r,allowUnknown:t=!0})=>({before:s=>{const{error:o,value:a}=r.validate(s.event,{allowUnknown:t,errors:{label:"key",wrap:{label:!1}}});if(o)throw e.error("Validation error",{error:o}),o.isJoi?new f(o.message):o;s.event=a}}),Z={isWarmingUp:e=>"serverless-plugin-warmup"===e.source},Q=(e={})=>{const r={...Z,...e};return{before:e=>{if(r.isWarmingUp(e.event))return"warmup"}}},X=[Q(),D,H()||$()?void 0:V].filter(Boolean),K=[l({serializers:[{regex:/^application\/xml$/,serializer:({body:e})=>`<message>${e}</message>`},{regex:/^application\/json$/,serializer:({body:e})=>JSON.stringify(e)},{regex:/^text\/plain$/,serializer:({body:e})=>e}],default:"application/json"}),{after:e=>{e.response=_(e.response)},onError:r=>{e.error(r.error,"Request failed"),r.response=_({statusCode:r.error.code||500,message:r.error.message||"Error"})}},(({authFunctionName:r="spytec-web-api-auth-prod-AuthorizerFunction",enabled:t=!!process.env.IS_OFFLINE}={})=>{const s=new u({region:process.env.AWS_REGION});return{before:async o=>{if(!t)return;const{event:a}=o,n=a.headers?.Authorization||a.headers?.authorization;if(!n||!n.startsWith("Bearer "))throw new Error("Authorization header is missing or invalid");const i={authorizationToken:n.slice(7)},l=new p({FunctionName:r,Payload:Buffer.from(JSON.stringify(i))});try{const e=await s.send(l),r=JSON.parse(Buffer.from(e.Payload).toString());if(r.errorMessage)throw new Error(r.errorMessage);a.requestContext.authorizer=r.context}catch(r){throw e.error("Error invoking auth function:",r),new Error("Authorization failed")}}}})()],Y=e=>i(e).use([...X]),ee=e=>i(e).use([...X,...K]);class re{collectionName;constructor(e){this.collectionName=e??`${process.env.AWS_LAMBDA_FUNCTION_NAME}-${process.env.AWS_LAMBDA_FUNCTION_VERSION}`,global.CACHE_STORAGE||(global.CACHE_STORAGE={}),global.CACHE_STORAGE[this.collectionName]||(global.CACHE_STORAGE[this.collectionName]=new Map)}set(e,r,t){const s=1e3*t+Date.now();global.CACHE_STORAGE[this.collectionName].set(e,{value:r,expire:s})}get(e){if(!e)throw new Error("key is required!");const r=global.CACHE_STORAGE[this.collectionName].get(e);return r?!r.expire||r.expire>Date.now()?r.value:this.remove(e):null}remove(e){global.CACHE_STORAGE[this.collectionName].get(e)&&global.CACHE_STORAGE[this.collectionName].delete(e)}}const te=async(e,r,t,s)=>{let o=r.get(t);return o||(o=await e(),r.set(t,o,s)),o},se=(e,r,t=new Error("Promise timed out"))=>{const s=new Promise(((e,s)=>{setTimeout((()=>{s(t)}),r)}));return Promise.race([e,s])};export{f as BadRequestError,w as BaseError,C as ConflictError,E as ForbiddenError,b as HttpError,re as LambdaCache,x as NotFoundError,y as SpytecJoi,A as UnauthorizedError,z as apiGatewayEventWrapper,K as apiGatewayMiddlewares,ee as apiGatewayMiddy,X as baseMiddlewares,_ as buildProxyResult,R as buildResponseBody,j as defaultApiSchema,q as getAuthorizerValidator,M as getEnvKey,N as getRequestContextValidator,h as iccidSchema,g as imeiSchema,k as isDev,H as isLocal,G as isProduction,$ as isTest,m as json,Y as middy,te as promiseWithCache,se as promiseWithTimeout,I as requestContextValidator,W as setupEnvConfig,T as sqsEventWrapper,P as sqsEventWrapperWithReturn,v as urlEncoded,S as validateEvent,L as validatorMiddleware,Q as warmupMiddleware};
@@ -0,0 +1,2 @@
1
+ import { logger, withRequest } from '@spytecgps/sdk-logger';
2
+ export { logger, withRequest };
package/dist/types.d.ts CHANGED
@@ -46,9 +46,6 @@ export interface HandlerResponse<R> {
46
46
  headers?: {
47
47
  [header: string]: boolean | number | string;
48
48
  };
49
- multiValueHeaders?: {
50
- [header: string]: (boolean | number | string)[];
51
- };
52
49
  isBase64Encoded?: boolean;
53
50
  message?: string;
54
51
  data?: R;
@@ -75,10 +72,3 @@ export type SQSWrapperArgsWithReturn<RecordType extends BaseRecord, T> = {
75
72
  singleHandler?: (record: RecordType) => T | Promise<T>;
76
73
  mode?: 'serial' | 'parallel';
77
74
  };
78
- export interface SpytecAuthContextV4 {
79
- type: AuthClass;
80
- userId?: string;
81
- clientId: number;
82
- principalId: string;
83
- scope?: string;
84
- }
@@ -1,14 +1,14 @@
1
1
  import { APIGatewayEventRequestContextWithAuthorizer } from 'aws-lambda/common/api-gateway';
2
2
  import * as Joi from 'joi';
3
- import { SpytecAuthContext, SpytecAuthContextV4 } from '../types';
4
- type GetAuthorizerValidatorParams = Partial<Record<keyof SpytecAuthContext, Joi.AnySchema>>;
5
- type GetAuthorizerValidatorParamsV4 = Partial<Record<keyof SpytecAuthContextV4, Joi.AnySchema>>;
6
- export declare const getAuthorizerValidator: (params?: GetAuthorizerValidatorParams) => Joi.ObjectSchema<SpytecAuthContext>;
7
- export declare const getAuthorizerValidatorV4: (params?: GetAuthorizerValidatorParamsV4) => Joi.ObjectSchema<SpytecAuthContext>;
3
+ import { AuthClass, SpytecAuthContext } from '../types';
4
+ interface GetAuthorizerValidatorParams {
5
+ scope?: string;
6
+ type?: AuthClass;
7
+ }
8
+ export declare const getAuthorizerValidator: ({ scope, type }?: GetAuthorizerValidatorParams) => Joi.ObjectSchema<SpytecAuthContext>;
8
9
  /**
9
10
  * @deprecated
10
11
  */
11
12
  export declare const requestContextValidator: Joi.ObjectSchema<any>;
12
13
  export declare const getRequestContextValidator: (params?: GetAuthorizerValidatorParams) => Joi.ObjectSchema<APIGatewayEventRequestContextWithAuthorizer<SpytecAuthContext>>;
13
- export declare const getRequestContextValidatorV4: (params?: GetAuthorizerValidatorParamsV4) => Joi.ObjectSchema<APIGatewayEventRequestContextWithAuthorizer<SpytecAuthContextV4>>;
14
14
  export {};
@@ -5,4 +5,4 @@ export declare const buildResponseBody: <T>(statusCode: number, message: string,
5
5
  message: string;
6
6
  result: T | undefined;
7
7
  };
8
- export declare const buildProxyResult: <R>({ statusCode, message, data, headers, multiValueHeaders, rawResult, stringifyBody, }: HandlerResponse<R>) => APIGatewayProxyResult;
8
+ export declare const buildProxyResult: <R>({ statusCode, message, data, headers, rawResult, stringifyBody, }: HandlerResponse<R>) => APIGatewayProxyResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spytecgps/lambda-utils",
3
- "version": "2.3.27",
3
+ "version": "2.4.0",
4
4
  "description": "Lambda Utils",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -33,22 +33,19 @@
33
33
  "@middy/http-response-serializer": "^2.5.7",
34
34
  "@middy/input-output-logger": "^2.5.7",
35
35
  "@middy/sqs-json-body-parser": "^2.5.7",
36
- "dayjs": "^1.11.13",
36
+ "@spytecgps/sdk-logger": "^2.0.6",
37
+ "dayjs": "^1.11.11",
37
38
  "deepmerge": "^4.3.1",
38
- "joi": "^17.13.3",
39
+ "joi": "^17.13.0",
39
40
  "qs": "^6.10.1"
40
41
  },
41
- "peerDependencies": {
42
- "@spytecgps/sdk-logger": "^2.0.8"
43
- },
44
42
  "devDependencies": {
45
- "@aws-sdk/client-lambda": "^3.731.1",
46
- "@rollup/plugin-commonjs": "^28.0.2",
43
+ "@aws-sdk/client-lambda": "^3.699.0",
44
+ "@rollup/plugin-commonjs": "^23.0.2",
47
45
  "@rollup/plugin-json": "^6.1.0",
48
- "@rollup/plugin-node-resolve": "^16.0.0",
46
+ "@rollup/plugin-node-resolve": "^15.0.1",
49
47
  "@rollup/plugin-terser": "^0.4.4",
50
- "@rollup/plugin-typescript": "^12.1.2",
51
- "@spytecgps/sdk-logger": "^2.0.8",
48
+ "@rollup/plugin-typescript": "^9.0.2",
52
49
  "@types/aws-lambda": "^8.10.76",
53
50
  "@types/jest": "^29.5.12",
54
51
  "@types/joi": "^17.2.3",
@@ -69,9 +66,10 @@
69
66
  "rollup": "^4.24.0",
70
67
  "rollup-plugin-peer-deps-external": "^2.2.4",
71
68
  "ts-jest": "^29.0.3",
69
+ "ts-loader": "^9.5.1",
72
70
  "typescript": "^5.6.3"
73
71
  },
74
72
  "files": [
75
73
  "dist/**/*"
76
74
  ]
77
- }
75
+ }
@@ -1,3 +0,0 @@
1
- export declare const normalizerMiddleware: () => {
2
- before: (request: any) => Promise<void>;
3
- };