serverless-spy 0.0.36 → 0.0.37

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.
Files changed (105) hide show
  1. package/.jsii +3 -3
  2. package/cli/cli.ts +145 -75
  3. package/cli/icons/Arch_AWS-Lambda_16.svg +18 -0
  4. package/cli/icons/Arch_Amazon-DynamoDB_16.svg +18 -0
  5. package/cli/icons/Arch_Amazon-EventBridge_16.svg +18 -0
  6. package/cli/icons/Arch_Amazon-Simple-Notification-Service_16.svg +18 -0
  7. package/cli/icons/Arch_Amazon-Simple-Queue-Service_16.svg +18 -0
  8. package/cli/icons/Arch_Amazon-Simple-Storage-Service_16.svg +18 -0
  9. package/cli/index.html +84 -25
  10. package/cli/node_modules/commander/LICENSE +22 -0
  11. package/cli/node_modules/commander/Readme.md +1114 -0
  12. package/cli/node_modules/commander/esm.mjs +16 -0
  13. package/cli/node_modules/commander/index.js +27 -0
  14. package/cli/node_modules/commander/lib/argument.js +147 -0
  15. package/cli/node_modules/commander/lib/command.js +2161 -0
  16. package/cli/node_modules/commander/lib/error.js +45 -0
  17. package/cli/node_modules/commander/lib/help.js +406 -0
  18. package/cli/node_modules/commander/lib/option.js +324 -0
  19. package/cli/node_modules/commander/lib/suggestSimilar.js +100 -0
  20. package/cli/node_modules/commander/package-support.json +16 -0
  21. package/cli/node_modules/commander/package.json +80 -0
  22. package/cli/node_modules/commander/typings/index.d.ts +879 -0
  23. package/cli/package.json +23 -0
  24. package/cli/sampleData.ts +518 -0
  25. package/cli/style.css +66 -42
  26. package/cli/webServerlessSpy.ts +461 -0
  27. package/common/SpyEventSender.ts +291 -0
  28. package/common/getWebSocketUrl.ts +21 -4
  29. package/common/spyEvents/EventBridgeBaseSpyEvent.ts +13 -0
  30. package/common/spyEvents/EventBridgeRuleSpyEvent.ts +2 -7
  31. package/common/spyEvents/EventBridgeSpyEvent.ts +2 -7
  32. package/common/spyEvents/FunctionBaseSpyEvent.ts +7 -0
  33. package/common/spyEvents/FunctionConsole.ts +5 -0
  34. package/common/spyEvents/FunctionConsoleSpyEvent.ts +5 -8
  35. package/common/spyEvents/FunctionResponseSpyEvent.ts +2 -5
  36. package/common/spyEvents/SnsSpyEventBase.ts +11 -0
  37. package/common/spyEvents/SnsSubscriptionSpyEvent.ts +3 -9
  38. package/common/spyEvents/SnsTopicSpyEvent.ts +3 -9
  39. package/dist/releasetag.txt +1 -1
  40. package/extension/interceptor.ts +91 -14
  41. package/functions/sendMessage.ts +4 -2
  42. package/lib/cli/cli.js +124 -65
  43. package/lib/cli/cli.mjs +125 -66
  44. package/lib/cli/sampleData.d.ts +892 -0
  45. package/lib/cli/sampleData.js +481 -0
  46. package/lib/cli/sampleData.mjs +478 -0
  47. package/lib/cli/webServerlessSpy.js +5516 -0
  48. package/lib/cli/webServerlessSpy.js.map +7 -0
  49. package/lib/common/SpyEventSender.d.ts +17 -0
  50. package/lib/common/SpyEventSender.js +227 -0
  51. package/lib/common/SpyEventSender.mjs +223 -0
  52. package/lib/common/getWebSocketUrl.d.ts +1 -1
  53. package/lib/common/getWebSocketUrl.js +19 -7
  54. package/lib/common/getWebSocketUrl.mjs +17 -5
  55. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.d.ts +9 -0
  56. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.js +3 -0
  57. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.mjs +2 -0
  58. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.d.ts +2 -7
  59. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.js +1 -1
  60. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.mjs +1 -1
  61. package/lib/common/spyEvents/EventBridgeSpyEvent.d.ts +2 -7
  62. package/lib/common/spyEvents/EventBridgeSpyEvent.js +1 -1
  63. package/lib/common/spyEvents/EventBridgeSpyEvent.mjs +1 -1
  64. package/lib/common/spyEvents/FunctionBaseSpyEvent.d.ts +6 -0
  65. package/lib/common/spyEvents/FunctionBaseSpyEvent.js +3 -0
  66. package/lib/common/spyEvents/FunctionBaseSpyEvent.mjs +2 -0
  67. package/lib/common/spyEvents/FunctionConsole.d.ts +5 -0
  68. package/lib/common/spyEvents/FunctionConsole.js +3 -0
  69. package/lib/common/spyEvents/FunctionConsole.mjs +2 -0
  70. package/lib/common/spyEvents/FunctionConsoleSpyEvent.d.ts +4 -8
  71. package/lib/common/spyEvents/FunctionConsoleSpyEvent.js +1 -1
  72. package/lib/common/spyEvents/FunctionConsoleSpyEvent.mjs +1 -1
  73. package/lib/common/spyEvents/FunctionResponseSpyEvent.d.ts +2 -5
  74. package/lib/common/spyEvents/FunctionResponseSpyEvent.js +1 -1
  75. package/lib/common/spyEvents/FunctionResponseSpyEvent.mjs +1 -1
  76. package/lib/common/spyEvents/SnsSpyEventBase.d.ts +10 -0
  77. package/lib/common/spyEvents/SnsSpyEventBase.js +3 -0
  78. package/lib/common/spyEvents/SnsSpyEventBase.mjs +2 -0
  79. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.d.ts +2 -9
  80. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.js +1 -1
  81. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.mjs +1 -1
  82. package/lib/common/spyEvents/SnsTopicSpyEvent.d.ts +2 -9
  83. package/lib/common/spyEvents/SnsTopicSpyEvent.js +1 -1
  84. package/lib/common/spyEvents/SnsTopicSpyEvent.mjs +1 -1
  85. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js +251 -181
  86. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js.map +3 -3
  87. package/lib/listener/SpyHandlers.ts.d.ts +30 -2
  88. package/lib/listener/SpyHandlers.ts.js +1 -1
  89. package/lib/listener/SpyHandlers.ts.mjs +1 -1
  90. package/lib/listener/WsListener.js +11 -11
  91. package/lib/listener/WsListener.mjs +12 -12
  92. package/lib/src/ServerlessSpy.js +2 -3
  93. package/lib/src/ServerlessSpy.mjs +1 -2
  94. package/listener/SpyHandlers.ts.ts +70 -9
  95. package/listener/WsListener.ts +21 -18
  96. package/package.json +5 -3
  97. package/cli/serverlessSpy.js +0 -73
  98. package/cli/ws.ts +0 -79
  99. package/common/publishSpyEvent.ts +0 -269
  100. package/lib/cli/ws.d.ts +0 -1
  101. package/lib/cli/ws.js +0 -68
  102. package/lib/cli/ws.mjs +0 -66
  103. package/lib/common/publishSpyEvent.d.ts +0 -4
  104. package/lib/common/publishSpyEvent.js +0 -211
  105. package/lib/common/publishSpyEvent.mjs +0 -205
@@ -0,0 +1,291 @@
1
+ import {
2
+ ApiGatewayManagementApi,
3
+ PostToConnectionCommand,
4
+ } from '@aws-sdk/client-apigatewaymanagementapi';
5
+ import {
6
+ AttributeValue,
7
+ DeleteItemCommand,
8
+ DynamoDBClient,
9
+ ScanCommand,
10
+ } from '@aws-sdk/client-dynamodb';
11
+
12
+ import { unmarshall } from '@aws-sdk/util-dynamodb';
13
+ import {
14
+ DynamoDBStreamEvent,
15
+ S3Event,
16
+ SNSEvent,
17
+ EventBridgeEvent,
18
+ SQSEvent,
19
+ } from 'aws-lambda';
20
+ import { envVariableNames } from '../src/common/envVariableNames';
21
+ import { DynamoDBSpyEvent } from './spyEvents/DynamoDBSpyEvent';
22
+ import { EventBridgeRuleSpyEvent } from './spyEvents/EventBridgeRuleSpyEvent';
23
+ import { EventBridgeSpyEvent } from './spyEvents/EventBridgeSpyEvent';
24
+ import { S3SpyEvent } from './spyEvents/S3SpyEvent';
25
+ import { SnsSubscriptionSpyEvent } from './spyEvents/SnsSubscriptionSpyEvent';
26
+ import { SnsTopicSpyEvent } from './spyEvents/SnsTopicSpyEvent';
27
+ import { SpyMessage } from './spyEvents/SpyMessage';
28
+ import { SqsSpyEvent } from './spyEvents/SqsSpyEvent';
29
+
30
+ export class SpyEventSender {
31
+ ddb = new DynamoDBClient({
32
+ region: process.env.AWS_REGION,
33
+ });
34
+ debugMode = process.env[envVariableNames.SSPY_DEBUG] === 'true';
35
+ apigwManagementApi = new ApiGatewayManagementApi({
36
+ apiVersion: '2018-11-29',
37
+ endpoint: process.env[envVariableNames.SSPY_WS_ENDPOINT]!,
38
+ });
39
+ connections: Record<string, AttributeValue>[] | undefined;
40
+
41
+ constructor(params?: {
42
+ log: (message: string, ...optionalParams: any[]) => void;
43
+ logError: (message: string, ...optionalParams: any[]) => void;
44
+ }) {
45
+ if (params?.log) {
46
+ this.log = params.log;
47
+ }
48
+
49
+ if (params?.logError) {
50
+ this.logError = params.logError;
51
+ }
52
+ }
53
+
54
+ public async publishSpyEvent(event: any) {
55
+ this.log('Event', JSON.stringify(event));
56
+
57
+ const mapping = JSON.parse(
58
+ process.env[envVariableNames.SSPY_INFRA_MAPPING]!
59
+ );
60
+ this.log('ARN to names mapping', JSON.stringify(mapping));
61
+
62
+ let connectionData;
63
+
64
+ const scanParams = new ScanCommand({
65
+ TableName: process.env[envVariableNames.SSPY_WS_TABLE_NAME] as string,
66
+ ProjectionExpression: 'connectionId',
67
+ });
68
+
69
+ connectionData = await this.ddb.send(scanParams);
70
+
71
+ this.connections = connectionData.Items;
72
+
73
+ const postDataPromises: Promise<any>[] = [];
74
+
75
+ if (event?.Records && event.Records[0]?.Sns) {
76
+ //console.log('*** SNS ***');
77
+ const eventSns = event as SNSEvent;
78
+ for (const record of eventSns.Records) {
79
+ const subscriptionArn = record.EventSubscriptionArn;
80
+
81
+ let serviceKey: string;
82
+ if (mapping[subscriptionArn]) {
83
+ // subscription event that could contain filter based on existing subscription
84
+ serviceKey = mapping[subscriptionArn];
85
+ } else {
86
+ // catch all subscription
87
+ const topicArn = record.Sns.TopicArn;
88
+ serviceKey = mapping[topicArn];
89
+ }
90
+
91
+ let message: string;
92
+
93
+ try {
94
+ message = JSON.parse(record.Sns.Message);
95
+ } catch {
96
+ message = record.Sns.Message;
97
+ }
98
+
99
+ const spyEventType = this.getSpyEventType(serviceKey) as
100
+ | 'FunctionSnsTopic'
101
+ | 'FunctionSnsSubscription';
102
+
103
+ const data: SnsTopicSpyEvent | SnsSubscriptionSpyEvent = {
104
+ spyEventType,
105
+ message,
106
+ subject: record.Sns.Subject,
107
+ timestamp: record.Sns.Timestamp,
108
+ topicArn: record.Sns.TopicArn,
109
+ messageId: record.Sns.MessageId,
110
+ messageAttributes: record.Sns.MessageAttributes,
111
+ };
112
+
113
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
114
+ data,
115
+ serviceKey,
116
+ };
117
+ postDataPromises.push(this.postData(fluentEvent));
118
+ }
119
+ } else if (event?.Records && event.Records[0]?.eventSource === 'aws:sqs') {
120
+ //console.log('*** SQS ***');
121
+ const eventSqs = event as SQSEvent;
122
+ for (const record of eventSqs.Records) {
123
+ const subscriptionArn = record.eventSourceARN;
124
+
125
+ const serviceKey = mapping[subscriptionArn];
126
+ let body: string;
127
+
128
+ try {
129
+ body = JSON.parse(record.body);
130
+ } catch {
131
+ body = record.body;
132
+ }
133
+
134
+ const data: SqsSpyEvent = {
135
+ spyEventType: 'Sqs',
136
+ body,
137
+ messageAttributes: record.messageAttributes,
138
+ };
139
+
140
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
141
+ data,
142
+ serviceKey,
143
+ };
144
+ postDataPromises.push(this.postData(fluentEvent));
145
+ }
146
+ } else if (event?.Records && event.Records[0]?.s3) {
147
+ //console.log('*** S3 ***');
148
+ const eventS3 = event as S3Event;
149
+ for (const record of eventS3.Records) {
150
+ const bucketArn = record.s3.bucket.arn;
151
+
152
+ const data: S3SpyEvent = {
153
+ spyEventType: 'S3',
154
+ eventName: record.eventName,
155
+ eventTime: record.eventTime,
156
+ bucket: record.s3.bucket.name,
157
+ key: record.s3.object.key,
158
+ };
159
+
160
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
161
+ data,
162
+ serviceKey: mapping[bucketArn],
163
+ };
164
+ postDataPromises.push(this.postData(fluentEvent));
165
+ }
166
+ } else if (event.Records && event.Records[0]?.dynamodb) {
167
+ //console.log('*** DYNAMODB ***');
168
+ const eventDynamoDB = event as DynamoDBStreamEvent;
169
+ for (const record of eventDynamoDB.Records) {
170
+ let arn = record.eventSourceARN!;
171
+ arn = arn.substring(0, arn.indexOf('/stream/'));
172
+
173
+ const data: DynamoDBSpyEvent = {
174
+ spyEventType: 'DynamoDB',
175
+ eventName: record.eventName,
176
+ newImage: unmarshall(record.dynamodb?.NewImage as any),
177
+ keys: unmarshall(record.dynamodb?.Keys as any),
178
+ oldImage: record.dynamodb?.OldImage
179
+ ? unmarshall(record.dynamodb?.OldImage as any)
180
+ : undefined,
181
+ };
182
+
183
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
184
+ data,
185
+ serviceKey: mapping[arn],
186
+ };
187
+ postDataPromises.push(this.postData(fluentEvent));
188
+ }
189
+ } else if (
190
+ event.detail &&
191
+ event['detail-type'] &&
192
+ event.version &&
193
+ event.source
194
+ ) {
195
+ //console.log('*** EventBridge ***');
196
+ const eventEb = event as EventBridgeEvent<any, any>;
197
+
198
+ const serviceKey = mapping.eventBridge; // the is new lambda for each subscription
199
+
200
+ const spyEventType = this.getSpyEventType(serviceKey) as
201
+ | 'EventBridge'
202
+ | 'EventBridgeRule';
203
+
204
+ const message = eventEb.detail;
205
+
206
+ const data: EventBridgeSpyEvent | EventBridgeRuleSpyEvent = {
207
+ spyEventType,
208
+ detail: message,
209
+ detailType: eventEb['detail-type'],
210
+ eventBridgeId: eventEb['id'],
211
+ source: eventEb.source,
212
+ time: eventEb.time,
213
+ account: eventEb.account,
214
+ };
215
+
216
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
217
+ data,
218
+ serviceKey,
219
+ };
220
+ postDataPromises.push(this.postData(fluentEvent));
221
+ } else {
222
+ //console.log('*** OTHER ***');
223
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = event;
224
+ postDataPromises.push(this.postData(fluentEvent));
225
+ }
226
+
227
+ await Promise.all(postDataPromises);
228
+ }
229
+
230
+ private async postData(spyMessage: Omit<SpyMessage, 'timestamp'>) {
231
+ this.log('Post spy message', JSON.stringify(spyMessage));
232
+
233
+ if (!this.connections) {
234
+ return;
235
+ }
236
+
237
+ const postCalls = this.connections.map(async ({ connectionId }) => {
238
+ this.log(`Sending message to client: ${connectionId.S}`);
239
+
240
+ try {
241
+ const postToConnectionCommand = new PostToConnectionCommand({
242
+ ConnectionId: connectionId.S,
243
+ Data: JSON.stringify({
244
+ timestamp: new Date().toISOString(),
245
+ serviceKey: spyMessage.serviceKey,
246
+ data: spyMessage.data,
247
+ }) as any,
248
+ });
249
+
250
+ await this.apigwManagementApi.send(postToConnectionCommand);
251
+ } catch (e) {
252
+ this.logError(`Faild sending spy message to: ${connectionId.S}`, e);
253
+ if ((e as any).$metadata.httpStatusCode === 410) {
254
+ this.log(`Found stale connection, deleting ${connectionId}`);
255
+
256
+ const deleteParams = new DeleteItemCommand({
257
+ TableName: process.env[envVariableNames.SSPY_WS_TABLE_NAME],
258
+ Key: { connectionId },
259
+ });
260
+
261
+ await this.ddb.send(deleteParams);
262
+ } else {
263
+ throw e;
264
+ }
265
+ }
266
+ });
267
+
268
+ await Promise.all(postCalls);
269
+ this.log('Send spy message finish');
270
+ }
271
+
272
+ private getSpyEventType(serviceKey: string) {
273
+ if (!serviceKey) {
274
+ throw new Error('Missing serviceKey');
275
+ }
276
+
277
+ return serviceKey.substring(0, serviceKey.indexOf('#'));
278
+ }
279
+
280
+ private log(message: string, ...optionalParams: any[]) {
281
+ if (this.debugMode) {
282
+ console.debug('SSPY EXTENSION', message, ...optionalParams);
283
+ }
284
+ }
285
+
286
+ private logError(message: string, ...optionalParams: any[]) {
287
+ if (this.debugMode) {
288
+ console.error('SSPY EXTENSION', message, ...optionalParams);
289
+ }
290
+ }
291
+ }
@@ -4,9 +4,26 @@ import * as aws4 from 'aws4';
4
4
 
5
5
  // ""wss://m6g3w6ttdh.execute-api.eu-west-1.amazonaws.com/prod";"
6
6
 
7
- export async function getWebSocketUrl(url: string, credentials?: Credentials) {
8
- const urlParsed = parseUrl(url);
9
- const hostParts = urlParsed!.host.split('.');
7
+ export async function getSignedWebSocketUrl(
8
+ url: string,
9
+ credentials?: Credentials
10
+ ) {
11
+ let hostParts: string[];
12
+ let pathname: string;
13
+
14
+ if (!url) {
15
+ throw new Error(`Missing websocket URL`);
16
+ }
17
+
18
+ try {
19
+ new URL(url); //validate URL
20
+
21
+ const urlParsed = parseUrl(url);
22
+ pathname = urlParsed!.pathname!;
23
+ hostParts = urlParsed!.host.split('.');
24
+ } catch {
25
+ throw new Error(`Invalid websocket URL ${url}`);
26
+ }
10
27
 
11
28
  if (!credentials) {
12
29
  const credentialsProvider = fromNodeProviderChain();
@@ -15,7 +32,7 @@ export async function getWebSocketUrl(url: string, credentials?: Credentials) {
15
32
 
16
33
  const AWS_REGION = hostParts[2]; // The region of your API-gateway
17
34
  const SOCKET_HOST = hostParts[0]; // Your API-gateway ID
18
- const ENV = urlParsed!.pathname!.replace(/^\//, ''); // The stage of your target deployment
35
+ const ENV = pathname.replace(/^\//, ''); // The stage of your target deployment
19
36
  const WEBSOCKET_URL = `${SOCKET_HOST}.execute-api.${AWS_REGION}.amazonaws.com`; // Don't prepend with wss!
20
37
  // const AWS_REGION = "eu-west-1"; // The region of your API-gateway
21
38
  // const SOCKET_HOST = "m6g3w6ttdh"; // Your API-gateway ID
@@ -0,0 +1,13 @@
1
+ import { SpyEvent } from './SpyEvent';
2
+
3
+ export interface EventBridgeBaseSpyEvent<
4
+ MessageType = any,
5
+ EventBridgeDetailType extends string = string
6
+ > extends SpyEvent {
7
+ eventBridgeId: string;
8
+ detail: MessageType;
9
+ detailType: EventBridgeDetailType;
10
+ source: string;
11
+ time: string;
12
+ account: string;
13
+ }
@@ -1,13 +1,8 @@
1
- import { SpyEvent } from './SpyEvent';
1
+ import { EventBridgeBaseSpyEvent } from './EventBridgeBaseSpyEvent';
2
2
 
3
3
  export interface EventBridgeRuleSpyEvent<
4
4
  MessageType = any,
5
5
  EventBridgeDetailType extends string = string
6
- > extends SpyEvent {
6
+ > extends EventBridgeBaseSpyEvent<MessageType, EventBridgeDetailType> {
7
7
  spyEventType: 'EventBridgeRule';
8
- detail: MessageType;
9
- detailType: EventBridgeDetailType;
10
- source: string;
11
- time: string;
12
- account: string;
13
8
  }
@@ -1,13 +1,8 @@
1
- import { SpyEvent } from './SpyEvent';
1
+ import { EventBridgeBaseSpyEvent } from './EventBridgeBaseSpyEvent';
2
2
 
3
3
  export interface EventBridgeSpyEvent<
4
4
  MessageType = any,
5
5
  EventBridgeDetailType extends string = string
6
- > extends SpyEvent {
6
+ > extends EventBridgeBaseSpyEvent<MessageType, EventBridgeDetailType> {
7
7
  spyEventType: 'EventBridge';
8
- detail: MessageType;
9
- detailType: EventBridgeDetailType;
10
- source: string;
11
- time: string;
12
- account: string;
13
8
  }
@@ -0,0 +1,7 @@
1
+ import { FunctionContext } from './FunctionContext';
2
+ import { SpyEvent } from './SpyEvent';
3
+
4
+ export interface FunctionBaseSpyEvent<TData = any> extends SpyEvent {
5
+ request: TData;
6
+ context: FunctionContext;
7
+ }
@@ -0,0 +1,5 @@
1
+ export type FunctionConsole = {
2
+ type: 'log' | 'debug' | 'info' | 'error' | 'warn';
3
+ message?: any;
4
+ optionalParams: any[];
5
+ };
@@ -1,11 +1,8 @@
1
- import { SpyEvent } from './SpyEvent';
1
+ import { FunctionBaseSpyEvent } from './FunctionBaseSpyEvent';
2
+ import { FunctionConsole } from './FunctionConsole';
2
3
 
3
- export interface FunctionConsoleSpyEvent<TRequest = any> extends SpyEvent {
4
+ export interface FunctionConsoleSpyEvent<TRequest = any>
5
+ extends FunctionBaseSpyEvent<TRequest> {
4
6
  spyEventType: 'FunctionConsole';
5
- request: TRequest;
6
- console: {
7
- type: 'log' | 'debug' | 'info' | 'error' | 'warn';
8
- message?: any;
9
- optionalParams: any[];
10
- };
7
+ console: FunctionConsole;
11
8
  }
@@ -1,10 +1,7 @@
1
- import { FunctionContext } from './FunctionContext';
2
- import { SpyEvent } from './SpyEvent';
1
+ import { FunctionBaseSpyEvent } from './FunctionBaseSpyEvent';
3
2
 
4
3
  export interface FunctionResponseSpyEvent<TRequest = any, TResponse = any>
5
- extends SpyEvent {
4
+ extends FunctionBaseSpyEvent<TRequest> {
6
5
  spyEventType: 'FunctionResponse';
7
- request: TRequest;
8
- context: FunctionContext;
9
6
  response: TResponse;
10
7
  }
@@ -0,0 +1,11 @@
1
+ import { SNSMessageAttributes } from 'aws-lambda';
2
+ import { SpyEvent } from './SpyEvent';
3
+
4
+ export interface SnsSpyEventBase<MessageType = any> extends SpyEvent {
5
+ message: MessageType;
6
+ subject: string;
7
+ timestamp: string;
8
+ topicArn: string;
9
+ messageId: string;
10
+ messageAttributes: SNSMessageAttributes;
11
+ }
@@ -1,12 +1,6 @@
1
- import { SNSMessageAttributes } from 'aws-lambda';
2
- import { SpyEvent } from './SpyEvent';
1
+ import { SnsSpyEventBase } from './SnsSpyEventBase';
3
2
 
4
- export interface SnsSubscriptionSpyEvent<MessageType = any> extends SpyEvent {
3
+ export interface SnsSubscriptionSpyEvent<MessageType = any>
4
+ extends SnsSpyEventBase<MessageType> {
5
5
  spyEventType: 'FunctionSnsSubscription';
6
- message: MessageType;
7
- subject: string;
8
- timestamp: string;
9
- topicArn: string;
10
- messageId: string;
11
- messageAttributes: SNSMessageAttributes;
12
6
  }
@@ -1,12 +1,6 @@
1
- import { SNSMessageAttributes } from 'aws-lambda';
2
- import { SpyEvent } from './SpyEvent';
1
+ import { SnsSpyEventBase } from './SnsSpyEventBase';
3
2
 
4
- export interface SnsTopicSpyEvent<MessageType = any> extends SpyEvent {
3
+ export interface SnsTopicSpyEvent<MessageType = any>
4
+ extends SnsSpyEventBase<MessageType> {
5
5
  spyEventType: 'FunctionSnsTopic';
6
- message: MessageType;
7
- subject: string;
8
- timestamp: string;
9
- topicArn: string;
10
- messageId: string;
11
- messageAttributes: SNSMessageAttributes;
12
6
  }
@@ -1 +1 @@
1
- v0.0.36
1
+ v0.0.37
@@ -1,10 +1,10 @@
1
- import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
2
1
  import { Callback, Context, Handler } from 'aws-lambda';
3
- import { publishSpyEvent } from '../common/publishSpyEvent';
2
+ import { FunctionConsoleSpyEvent } from '../common/spyEvents/FunctionConsoleSpyEvent';
4
3
  import { FunctionContext } from '../common/spyEvents/FunctionContext';
5
4
  import { FunctionErrorSpyEvent } from '../common/spyEvents/FunctionErrorSpyEvent';
6
5
  import { FunctionRequestSpyEvent } from '../common/spyEvents/FunctionRequestSpyEvent';
7
6
  import { FunctionResponseSpyEvent } from '../common/spyEvents/FunctionResponseSpyEvent';
7
+ import { SpyEventSender } from '../common/SpyEventSender';
8
8
  import { envVariableNames } from '../src/common/envVariableNames';
9
9
  import { load } from './aws/UserFunction';
10
10
 
@@ -15,6 +15,22 @@ const subscribedToSQS =
15
15
 
16
16
  const debugMode = process.env[envVariableNames.SSPY_DEBUG] === 'true';
17
17
 
18
+ const oldConsoleLog = console.log;
19
+ const oldConsoleWarn = console.warn;
20
+ const oldConsoleDebug = console.debug;
21
+ const oldConsoleInfo = console.info;
22
+ const oldConsoleError = console.error;
23
+ let currentEvent: any;
24
+ let currentContext: FunctionContext | undefined;
25
+ let promises: Promise<any>[] = [];
26
+
27
+ interceptConsole();
28
+
29
+ const spyEventSender = new SpyEventSender({
30
+ log,
31
+ logError,
32
+ });
33
+
18
34
  // Wrap original handler.
19
35
  // Handler can be async or non-async:
20
36
  // https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
@@ -23,9 +39,19 @@ export const handler = (
23
39
  context: Context,
24
40
  callback: Callback
25
41
  ): Promise<any> | undefined => {
26
- log('Request', JSON.stringify(event));
42
+ const contextSpy: FunctionContext = {
43
+ functionName: context.functionName,
44
+ awsRequestId: context.awsRequestId,
45
+ identity: context.identity,
46
+ clientContext: context.clientContext,
47
+ };
48
+
49
+ currentEvent = event;
50
+ currentContext = contextSpy;
27
51
 
28
- const promises: Promise<any>[] = [];
52
+ promises = [];
53
+
54
+ log('Request', JSON.stringify(event));
29
55
 
30
56
  if (subscribedToSQS) {
31
57
  // send raw message
@@ -34,13 +60,6 @@ export const handler = (
34
60
  promises.push(p);
35
61
  }
36
62
 
37
- const contextSpy: FunctionContext = {
38
- functionName: context.functionName,
39
- awsRequestId: context.awsRequestId,
40
- identity: context.identity,
41
- clientContext: context.clientContext,
42
- };
43
-
44
63
  const key = `Function#${
45
64
  process.env[envVariableNames.SSPY_FUNCTION_NAME]
46
65
  }#Request`;
@@ -63,6 +82,8 @@ export const handler = (
63
82
  context: contextSpy,
64
83
  });
65
84
  promises.push(p);
85
+ currentEvent = undefined;
86
+ currentContext = undefined;
66
87
  return Promise.all(promises);
67
88
  };
68
89
 
@@ -77,6 +98,9 @@ export const handler = (
77
98
  context: contextSpy,
78
99
  });
79
100
  promises.push(p);
101
+ currentEvent = undefined;
102
+ currentContext = undefined;
103
+
80
104
  return Promise.all(promises);
81
105
  };
82
106
 
@@ -117,6 +141,59 @@ export const handler = (
117
141
  }
118
142
  };
119
143
 
144
+ function interceptConsole() {
145
+ const sendLogs = (
146
+ type: 'log' | 'debug' | 'info' | 'error' | 'warn',
147
+ args?: any[]
148
+ ) => {
149
+ if (!currentContext) return;
150
+
151
+ log(`Console ${type}`, JSON.stringify(args));
152
+ const message = args?.shift();
153
+
154
+ const key = `Function#${
155
+ process.env[envVariableNames.SSPY_FUNCTION_NAME]
156
+ }#Console`;
157
+
158
+ const p = sendLambdaSpyEvent(key, <FunctionConsoleSpyEvent>{
159
+ request: currentEvent,
160
+ context: currentContext,
161
+ console: {
162
+ type,
163
+ message,
164
+ optionalParams: args,
165
+ },
166
+ });
167
+
168
+ promises.push(p);
169
+ };
170
+
171
+ console.log = function (...args: any[]) {
172
+ sendLogs('log', args);
173
+ oldConsoleLog.apply(console, args);
174
+ };
175
+
176
+ console.warn = function (...args: any[]) {
177
+ sendLogs('warn', args);
178
+ oldConsoleWarn.apply(console, args);
179
+ };
180
+
181
+ console.debug = function (...args: any[]) {
182
+ sendLogs('debug', args);
183
+ oldConsoleDebug.apply(console, args);
184
+ };
185
+
186
+ console.info = function (...args: any[]) {
187
+ sendLogs('info', args);
188
+ oldConsoleInfo.apply(console, args);
189
+ };
190
+
191
+ console.error = function (...args: any[]) {
192
+ sendLogs('error', args);
193
+ oldConsoleError.apply(console, args);
194
+ };
195
+ }
196
+
120
197
  function isPromise(obj: any): boolean {
121
198
  return typeof obj?.then === 'function';
122
199
  }
@@ -136,7 +213,7 @@ async function sendLambdaSpyEvent(
136
213
  }
137
214
 
138
215
  async function sendRawSpyEvent(data: any) {
139
- await publishSpyEvent(data);
216
+ await spyEventSender.publishSpyEvent(data);
140
217
  }
141
218
 
142
219
  function getOriginalHandler(): Handler {
@@ -152,12 +229,12 @@ function getOriginalHandler(): Handler {
152
229
 
153
230
  function log(message: string, ...optionalParams: any[]) {
154
231
  if (debugMode) {
155
- console.debug('SSPY EXTENSION', message, ...optionalParams);
232
+ oldConsoleDebug('SSPY EXTENSION', message, ...optionalParams);
156
233
  }
157
234
  }
158
235
 
159
236
  function logError(message: string, ...optionalParams: any[]) {
160
237
  if (debugMode) {
161
- console.error('SSPY EXTENSION', message, ...optionalParams);
238
+ oldConsoleError('SSPY EXTENSION', message, ...optionalParams);
162
239
  }
163
240
  }
@@ -1,8 +1,10 @@
1
- import { publishSpyEvent } from '../common/publishSpyEvent';
1
+ import { SpyEventSender } from '../common/SpyEventSender';
2
+
3
+ const spyEventSender = new SpyEventSender();
2
4
 
3
5
  export const handler = async (event: any) => {
4
6
  try {
5
- await publishSpyEvent(event);
7
+ await spyEventSender.publishSpyEvent(event);
6
8
  } catch (e) {
7
9
  console.error(e);
8
10
  return { statusCode: 500, body: (e as Error)?.stack };