serverless-spy 0.0.35 → 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 (116) hide show
  1. package/.jsii +236 -9
  2. package/API.md +161 -0
  3. package/cli/cli.ts +145 -75
  4. package/cli/icons/Arch_AWS-Lambda_16.svg +18 -0
  5. package/cli/icons/Arch_Amazon-DynamoDB_16.svg +18 -0
  6. package/cli/icons/Arch_Amazon-EventBridge_16.svg +18 -0
  7. package/cli/icons/Arch_Amazon-Simple-Notification-Service_16.svg +18 -0
  8. package/cli/icons/Arch_Amazon-Simple-Queue-Service_16.svg +18 -0
  9. package/cli/icons/Arch_Amazon-Simple-Storage-Service_16.svg +18 -0
  10. package/cli/index.html +84 -25
  11. package/cli/node_modules/commander/LICENSE +22 -0
  12. package/cli/node_modules/commander/Readme.md +1114 -0
  13. package/cli/node_modules/commander/esm.mjs +16 -0
  14. package/cli/node_modules/commander/index.js +27 -0
  15. package/cli/node_modules/commander/lib/argument.js +147 -0
  16. package/cli/node_modules/commander/lib/command.js +2161 -0
  17. package/cli/node_modules/commander/lib/error.js +45 -0
  18. package/cli/node_modules/commander/lib/help.js +406 -0
  19. package/cli/node_modules/commander/lib/option.js +324 -0
  20. package/cli/node_modules/commander/lib/suggestSimilar.js +100 -0
  21. package/cli/node_modules/commander/package-support.json +16 -0
  22. package/cli/node_modules/commander/package.json +80 -0
  23. package/cli/node_modules/commander/typings/index.d.ts +879 -0
  24. package/cli/package.json +23 -0
  25. package/cli/sampleData.ts +518 -0
  26. package/cli/style.css +66 -42
  27. package/cli/webServerlessSpy.ts +461 -0
  28. package/common/SpyEventSender.ts +291 -0
  29. package/common/getWebSocketUrl.ts +21 -4
  30. package/common/spyEvents/EventBridgeBaseSpyEvent.ts +13 -0
  31. package/common/spyEvents/EventBridgeRuleSpyEvent.ts +2 -7
  32. package/common/spyEvents/EventBridgeSpyEvent.ts +2 -7
  33. package/common/spyEvents/FunctionBaseSpyEvent.ts +7 -0
  34. package/common/spyEvents/FunctionConsole.ts +5 -0
  35. package/common/spyEvents/FunctionConsoleSpyEvent.ts +5 -8
  36. package/common/spyEvents/FunctionResponseSpyEvent.ts +2 -5
  37. package/common/spyEvents/SnsSpyEventBase.ts +11 -0
  38. package/common/spyEvents/SnsSubscriptionSpyEvent.ts +3 -9
  39. package/common/spyEvents/SnsTopicSpyEvent.ts +3 -9
  40. package/dist/releasetag.txt +1 -1
  41. package/extension/interceptor.ts +107 -27
  42. package/functions/sendMessage.ts +4 -2
  43. package/functions/sqsSubscriptionAndDropAllMessages.ts +3 -0
  44. package/lib/cli/cli.js +124 -65
  45. package/lib/cli/cli.mjs +125 -66
  46. package/lib/cli/sampleData.d.ts +892 -0
  47. package/lib/cli/sampleData.js +481 -0
  48. package/lib/cli/sampleData.mjs +478 -0
  49. package/lib/cli/webServerlessSpy.js +5516 -0
  50. package/lib/cli/webServerlessSpy.js.map +7 -0
  51. package/lib/common/SpyEventSender.d.ts +17 -0
  52. package/lib/common/SpyEventSender.js +227 -0
  53. package/lib/common/SpyEventSender.mjs +223 -0
  54. package/lib/common/getWebSocketUrl.d.ts +1 -1
  55. package/lib/common/getWebSocketUrl.js +19 -7
  56. package/lib/common/getWebSocketUrl.mjs +17 -5
  57. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.d.ts +9 -0
  58. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.js +3 -0
  59. package/lib/common/spyEvents/EventBridgeBaseSpyEvent.mjs +2 -0
  60. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.d.ts +2 -7
  61. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.js +1 -1
  62. package/lib/common/spyEvents/EventBridgeRuleSpyEvent.mjs +1 -1
  63. package/lib/common/spyEvents/EventBridgeSpyEvent.d.ts +2 -7
  64. package/lib/common/spyEvents/EventBridgeSpyEvent.js +1 -1
  65. package/lib/common/spyEvents/EventBridgeSpyEvent.mjs +1 -1
  66. package/lib/common/spyEvents/FunctionBaseSpyEvent.d.ts +6 -0
  67. package/lib/common/spyEvents/FunctionBaseSpyEvent.js +3 -0
  68. package/lib/common/spyEvents/FunctionBaseSpyEvent.mjs +2 -0
  69. package/lib/common/spyEvents/FunctionConsole.d.ts +5 -0
  70. package/lib/common/spyEvents/FunctionConsole.js +3 -0
  71. package/lib/common/spyEvents/FunctionConsole.mjs +2 -0
  72. package/lib/common/spyEvents/FunctionConsoleSpyEvent.d.ts +4 -8
  73. package/lib/common/spyEvents/FunctionConsoleSpyEvent.js +1 -1
  74. package/lib/common/spyEvents/FunctionConsoleSpyEvent.mjs +1 -1
  75. package/lib/common/spyEvents/FunctionResponseSpyEvent.d.ts +2 -5
  76. package/lib/common/spyEvents/FunctionResponseSpyEvent.js +1 -1
  77. package/lib/common/spyEvents/FunctionResponseSpyEvent.mjs +1 -1
  78. package/lib/common/spyEvents/SnsSpyEventBase.d.ts +10 -0
  79. package/lib/common/spyEvents/SnsSpyEventBase.js +3 -0
  80. package/lib/common/spyEvents/SnsSpyEventBase.mjs +2 -0
  81. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.d.ts +2 -9
  82. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.js +1 -1
  83. package/lib/common/spyEvents/SnsSubscriptionSpyEvent.mjs +1 -1
  84. package/lib/common/spyEvents/SnsTopicSpyEvent.d.ts +2 -9
  85. package/lib/common/spyEvents/SnsTopicSpyEvent.js +1 -1
  86. package/lib/common/spyEvents/SnsTopicSpyEvent.mjs +1 -1
  87. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js +10793 -23825
  88. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js.map +4 -4
  89. package/lib/listener/ServerlessSpyListenerParams.d.ts +1 -0
  90. package/lib/listener/ServerlessSpyListenerParams.js +1 -1
  91. package/lib/listener/ServerlessSpyListenerParams.mjs +1 -1
  92. package/lib/listener/SpyHandlers.ts.d.ts +30 -2
  93. package/lib/listener/SpyHandlers.ts.js +1 -1
  94. package/lib/listener/SpyHandlers.ts.mjs +1 -1
  95. package/lib/listener/WsListener.d.ts +4 -21
  96. package/lib/listener/WsListener.js +21 -13
  97. package/lib/listener/WsListener.mjs +22 -14
  98. package/lib/src/ServerlessSpy.d.ts +44 -14
  99. package/lib/src/ServerlessSpy.js +228 -86
  100. package/lib/src/ServerlessSpy.mjs +227 -85
  101. package/lib/src/common/envVariableNames.d.ts +6 -2
  102. package/lib/src/common/envVariableNames.js +6 -2
  103. package/lib/src/common/envVariableNames.mjs +6 -2
  104. package/listener/ServerlessSpyListenerParams.ts +1 -0
  105. package/listener/SpyHandlers.ts.ts +70 -9
  106. package/listener/WsListener.ts +39 -24
  107. package/package.json +5 -3
  108. package/cli/serverlessSpy.js +0 -73
  109. package/cli/ws.ts +0 -79
  110. package/common/publishSpyEvent.ts +0 -269
  111. package/lib/cli/ws.d.ts +0 -1
  112. package/lib/cli/ws.js +0 -68
  113. package/lib/cli/ws.mjs +0 -66
  114. package/lib/common/publishSpyEvent.d.ts +0 -4
  115. package/lib/common/publishSpyEvent.js +0 -211
  116. 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.35
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
 
@@ -13,7 +13,23 @@ const ORIGINAL_HANDLER_KEY = 'ORIGINAL_HANDLER';
13
13
  const subscribedToSQS =
14
14
  process.env[envVariableNames.SSPY_SUBSCRIBED_TO_SQS] === 'true';
15
15
 
16
- const lambdaClient = new LambdaClient({});
16
+ const debugMode = process.env[envVariableNames.SSPY_DEBUG] === 'true';
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
+ });
17
33
 
18
34
  // Wrap original handler.
19
35
  // Handler can be async or non-async:
@@ -23,15 +39,6 @@ export const handler = (
23
39
  context: Context,
24
40
  callback: Callback
25
41
  ): Promise<any> | undefined => {
26
- const promises: Promise<any>[] = [];
27
-
28
- if (subscribedToSQS) {
29
- // send raw message
30
- console.log('EXTENSION: Send raw message for SQS');
31
- const p = sendRawSpyEvent(event);
32
- promises.push(p);
33
- }
34
-
35
42
  const contextSpy: FunctionContext = {
36
43
  functionName: context.functionName,
37
44
  awsRequestId: context.awsRequestId,
@@ -39,7 +46,20 @@ export const handler = (
39
46
  clientContext: context.clientContext,
40
47
  };
41
48
 
42
- console.log('EXTENSION REQUEST:', JSON.stringify(event));
49
+ currentEvent = event;
50
+ currentContext = contextSpy;
51
+
52
+ promises = [];
53
+
54
+ log('Request', JSON.stringify(event));
55
+
56
+ if (subscribedToSQS) {
57
+ // send raw message
58
+ log('Send raw message for SQS');
59
+ const p = sendRawSpyEvent(event);
60
+ promises.push(p);
61
+ }
62
+
43
63
  const key = `Function#${
44
64
  process.env[envVariableNames.SSPY_FUNCTION_NAME]
45
65
  }#Request`;
@@ -52,7 +72,7 @@ export const handler = (
52
72
  const originalHandler = getOriginalHandler();
53
73
 
54
74
  const fail = (error: any) => {
55
- console.error('FAIL', error);
75
+ logError(error);
56
76
  const key = `Function#${
57
77
  process.env[envVariableNames.SSPY_FUNCTION_NAME]
58
78
  }#Error`;
@@ -62,11 +82,13 @@ export const handler = (
62
82
  context: contextSpy,
63
83
  });
64
84
  promises.push(p);
85
+ currentEvent = undefined;
86
+ currentContext = undefined;
65
87
  return Promise.all(promises);
66
88
  };
67
89
 
68
90
  const succeed = (response: any) => {
69
- console.log('EXTENSION RESPONSE:', JSON.stringify(response));
91
+ log('Response', JSON.stringify(response));
70
92
  const key = `Function#${
71
93
  process.env[envVariableNames[envVariableNames.SSPY_FUNCTION_NAME]]
72
94
  }#Response`;
@@ -76,6 +98,9 @@ export const handler = (
76
98
  context: contextSpy,
77
99
  });
78
100
  promises.push(p);
101
+ currentEvent = undefined;
102
+ currentContext = undefined;
103
+
79
104
  return Promise.all(promises);
80
105
  };
81
106
 
@@ -116,6 +141,59 @@ export const handler = (
116
141
  }
117
142
  };
118
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
+
119
197
  function isPromise(obj: any): boolean {
120
198
  return typeof obj?.then === 'function';
121
199
  }
@@ -135,21 +213,11 @@ async function sendLambdaSpyEvent(
135
213
  }
136
214
 
137
215
  async function sendRawSpyEvent(data: any) {
138
- await publishSpyEvent(data);
139
-
140
- // console.log('EXTENSION SPY EVENT:', JSON.stringify(data));
141
-
142
- // const command = new InvokeCommand({
143
- // FunctionName: fluentTestSendFunctionName,
144
- // InvocationType: 'RequestResponse',
145
- // LogType: 'Tail',
146
- // Payload: JSON.stringify(data) as any,
147
- // });
148
- // await lambdaClient.send(command);
216
+ await spyEventSender.publishSpyEvent(data);
149
217
  }
150
218
 
151
219
  function getOriginalHandler(): Handler {
152
- console.log('ORIGINAL_HANDLER_KEY', process.env[ORIGINAL_HANDLER_KEY]);
220
+ log('Original handler', process.env[ORIGINAL_HANDLER_KEY]);
153
221
 
154
222
  if (process.env[ORIGINAL_HANDLER_KEY] === undefined)
155
223
  throw Error('Missing original handler');
@@ -158,3 +226,15 @@ function getOriginalHandler(): Handler {
158
226
  process.env[ORIGINAL_HANDLER_KEY]
159
227
  ) as Handler;
160
228
  }
229
+
230
+ function log(message: string, ...optionalParams: any[]) {
231
+ if (debugMode) {
232
+ oldConsoleDebug('SSPY EXTENSION', message, ...optionalParams);
233
+ }
234
+ }
235
+
236
+ function logError(message: string, ...optionalParams: any[]) {
237
+ if (debugMode) {
238
+ oldConsoleError('SSPY EXTENSION', message, ...optionalParams);
239
+ }
240
+ }