serverless-spy 0.0.32 → 0.0.34

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 (49) hide show
  1. package/.jsii +9 -9
  2. package/common/publishSpyEvent.ts +269 -0
  3. package/dist/releasetag.txt +1 -1
  4. package/extension/interceptor.ts +24 -13
  5. package/functions/onConnect.ts +2 -1
  6. package/functions/onDisconnect.ts +2 -1
  7. package/functions/sendMessage.ts +3 -268
  8. package/lib/common/publishSpyEvent.d.ts +4 -0
  9. package/lib/common/publishSpyEvent.js +211 -0
  10. package/lib/common/publishSpyEvent.mjs +205 -0
  11. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js +13890 -13
  12. package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js.map +4 -4
  13. package/lib/listener/{SpyListener.d.ts → ServerlessSpyListener.d.ts} +12 -12
  14. package/lib/listener/ServerlessSpyListener.js +3 -0
  15. package/lib/listener/ServerlessSpyListener.mjs +2 -0
  16. package/lib/listener/ServerlessSpyListenerParams.d.ts +5 -0
  17. package/lib/listener/ServerlessSpyListenerParams.js +3 -0
  18. package/lib/listener/ServerlessSpyListenerParams.mjs +2 -0
  19. package/lib/listener/SpyHandlers.ts.d.ts +1 -1
  20. package/lib/listener/SpyHandlers.ts.js +1 -1
  21. package/lib/listener/SpyHandlers.ts.mjs +1 -1
  22. package/lib/listener/WaitForParams.d.ts +5 -0
  23. package/lib/listener/WaitForParams.js +3 -0
  24. package/lib/listener/WaitForParams.mjs +2 -0
  25. package/lib/listener/WsListener.d.ts +38 -0
  26. package/lib/listener/WsListener.js +185 -0
  27. package/lib/listener/WsListener.mjs +181 -0
  28. package/lib/listener/createServerlessSpyListener.d.ts +2 -13
  29. package/lib/listener/createServerlessSpyListener.js +5 -163
  30. package/lib/listener/createServerlessSpyListener.mjs +5 -163
  31. package/lib/listener/index.d.ts +1 -1
  32. package/lib/listener/index.js +2 -2
  33. package/lib/listener/index.mjs +2 -2
  34. package/lib/src/ServerlessSpy.d.ts +16 -8
  35. package/lib/src/ServerlessSpy.js +94 -53
  36. package/lib/src/ServerlessSpy.mjs +93 -52
  37. package/lib/src/common/envVariableNames.d.ts +8 -0
  38. package/lib/src/common/envVariableNames.js +15 -0
  39. package/lib/src/common/envVariableNames.mjs +12 -0
  40. package/listener/{SpyListener.ts → ServerlessSpyListener.ts} +12 -13
  41. package/listener/ServerlessSpyListenerParams.ts +6 -0
  42. package/listener/SpyHandlers.ts.ts +1 -1
  43. package/listener/WaitForParams.ts +6 -0
  44. package/listener/WsListener.ts +273 -0
  45. package/listener/createServerlessSpyListener.ts +5 -265
  46. package/listener/index.ts +1 -1
  47. package/package.json +1 -1
  48. package/lib/listener/SpyListener.js +0 -3
  49. package/lib/listener/SpyListener.mjs +0 -2
package/.jsii CHANGED
@@ -3149,7 +3149,7 @@
3149
3149
  },
3150
3150
  "locationInModule": {
3151
3151
  "filename": "src/ServerlessSpy.ts",
3152
- "line": 43
3152
+ "line": 37
3153
3153
  },
3154
3154
  "parameters": [
3155
3155
  {
@@ -3176,7 +3176,7 @@
3176
3176
  "kind": "class",
3177
3177
  "locationInModule": {
3178
3178
  "filename": "src/ServerlessSpy.ts",
3179
- "line": 24
3179
+ "line": 25
3180
3180
  },
3181
3181
  "methods": [
3182
3182
  {
@@ -3185,7 +3185,7 @@
3185
3185
  },
3186
3186
  "locationInModule": {
3187
3187
  "filename": "src/ServerlessSpy.ts",
3188
- "line": 469
3188
+ "line": 493
3189
3189
  },
3190
3190
  "name": "getConstructName",
3191
3191
  "parameters": [
@@ -3211,7 +3211,7 @@
3211
3211
  },
3212
3212
  "locationInModule": {
3213
3213
  "filename": "src/ServerlessSpy.ts",
3214
- "line": 39
3214
+ "line": 34
3215
3215
  },
3216
3216
  "name": "serviceKeys",
3217
3217
  "type": {
@@ -3229,7 +3229,7 @@
3229
3229
  },
3230
3230
  "locationInModule": {
3231
3231
  "filename": "src/ServerlessSpy.ts",
3232
- "line": 41
3232
+ "line": 35
3233
3233
  },
3234
3234
  "name": "wsUrl",
3235
3235
  "type": {
@@ -3249,7 +3249,7 @@
3249
3249
  "kind": "interface",
3250
3250
  "locationInModule": {
3251
3251
  "filename": "src/ServerlessSpy.ts",
3252
- "line": 20
3252
+ "line": 21
3253
3253
  },
3254
3254
  "name": "ServerlessSpyProps",
3255
3255
  "properties": [
@@ -3261,7 +3261,7 @@
3261
3261
  "immutable": true,
3262
3262
  "locationInModule": {
3263
3263
  "filename": "src/ServerlessSpy.ts",
3264
- "line": 21
3264
+ "line": 22
3265
3265
  },
3266
3266
  "name": "generateSpyEventsFileLocation",
3267
3267
  "optional": true,
@@ -3273,6 +3273,6 @@
3273
3273
  "symbolId": "src/ServerlessSpy:ServerlessSpyProps"
3274
3274
  }
3275
3275
  },
3276
- "version": "0.0.32",
3277
- "fingerprint": "Lly28xuuNsA7KsQvFz88vDGS3Ud6VJ5mBWvOmE0UlaE="
3276
+ "version": "0.0.34",
3277
+ "fingerprint": "VlGyOqTBqt3S7lh687Y1De02BbMROvvJmMqb0uEIX00="
3278
3278
  }
@@ -0,0 +1,269 @@
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
+ const ddb = new DynamoDBClient({
31
+ region: process.env.AWS_REGION,
32
+ });
33
+
34
+ const apigwManagementApi = new ApiGatewayManagementApi({
35
+ apiVersion: '2018-11-29',
36
+ endpoint: process.env[envVariableNames.WS_ENDPOINT]!,
37
+ });
38
+
39
+ let connections: Record<string, AttributeValue>[] | undefined;
40
+
41
+ export async function publishSpyEvent(event: any) {
42
+ console.log('EVENT', JSON.stringify(event));
43
+
44
+ const mapping = JSON.parse(process.env[envVariableNames.INFRA_MAPPING]!);
45
+ console.log('mapping', JSON.stringify(mapping));
46
+
47
+ let connectionData;
48
+
49
+ const scanParams = new ScanCommand({
50
+ TableName: process.env[envVariableNames.TABLE_NAME] as string,
51
+ ProjectionExpression: 'connectionId',
52
+ });
53
+
54
+ connectionData = await ddb.send(scanParams);
55
+
56
+ connections = connectionData.Items;
57
+
58
+ console.log(
59
+ `connections at ${new Date().toISOString()}`,
60
+ JSON.stringify(connections)
61
+ );
62
+
63
+ const postDataPromises: Promise<any>[] = [];
64
+
65
+ if (event?.Records && event.Records[0]?.Sns) {
66
+ console.log('*** SNS ***');
67
+ const eventSns = event as SNSEvent;
68
+ for (const record of eventSns.Records) {
69
+ const subscriptionArn = record.EventSubscriptionArn;
70
+
71
+ let serviceKey: string;
72
+ if (mapping[subscriptionArn]) {
73
+ // subscription event that could contain filter based on existing subscription
74
+ serviceKey = mapping[subscriptionArn];
75
+ } else {
76
+ // catch all subscription
77
+ const topicArn = record.Sns.TopicArn;
78
+ serviceKey = mapping[topicArn];
79
+ }
80
+
81
+ let message: string;
82
+
83
+ try {
84
+ message = JSON.parse(record.Sns.Message);
85
+ } catch {
86
+ message = record.Sns.Message;
87
+ }
88
+
89
+ const spyEventType = getSpyEventType(serviceKey) as
90
+ | 'FunctionSnsTopic'
91
+ | 'FunctionSnsSubscription';
92
+
93
+ const data: SnsTopicSpyEvent | SnsSubscriptionSpyEvent = {
94
+ spyEventType,
95
+ message,
96
+ subject: record.Sns.Subject,
97
+ timestamp: record.Sns.Timestamp,
98
+ topicArn: record.Sns.TopicArn,
99
+ messageId: record.Sns.MessageId,
100
+ messageAttributes: record.Sns.MessageAttributes,
101
+ };
102
+
103
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
104
+ data,
105
+ serviceKey,
106
+ };
107
+ postDataPromises.push(postData(fluentEvent));
108
+ }
109
+ } else if (event?.Records && event.Records[0]?.eventSource === 'aws:sqs') {
110
+ console.log('*** SQS ***');
111
+ const eventSqs = event as SQSEvent;
112
+ for (const record of eventSqs.Records) {
113
+ const subscriptionArn = record.eventSourceARN;
114
+
115
+ const serviceKey = mapping[subscriptionArn];
116
+ let body: string;
117
+
118
+ try {
119
+ body = JSON.parse(record.body);
120
+ } catch {
121
+ body = record.body;
122
+ }
123
+
124
+ const data: SqsSpyEvent = {
125
+ spyEventType: 'Sqs',
126
+ body,
127
+ messageAttributes: record.messageAttributes,
128
+ };
129
+
130
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
131
+ data,
132
+ serviceKey,
133
+ };
134
+ postDataPromises.push(postData(fluentEvent));
135
+ }
136
+ } else if (event?.Records && event.Records[0]?.s3) {
137
+ console.log('*** S3 ***');
138
+ const eventS3 = event as S3Event;
139
+ for (const record of eventS3.Records) {
140
+ const bucketArn = record.s3.bucket.arn;
141
+
142
+ const data: S3SpyEvent = {
143
+ spyEventType: 'S3',
144
+ eventName: record.eventName,
145
+ eventTime: record.eventTime,
146
+ bucket: record.s3.bucket.name,
147
+ key: record.s3.object.key,
148
+ };
149
+
150
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
151
+ data,
152
+ serviceKey: mapping[bucketArn],
153
+ };
154
+ postDataPromises.push(postData(fluentEvent));
155
+ }
156
+ } else if (event.Records && event.Records[0]?.dynamodb) {
157
+ console.log('*** DYNAMODB ***');
158
+ const eventDynamoDB = event as DynamoDBStreamEvent;
159
+ for (const record of eventDynamoDB.Records) {
160
+ let arn = record.eventSourceARN!;
161
+ arn = arn.substring(0, arn.indexOf('/stream/'));
162
+
163
+ const data: DynamoDBSpyEvent = {
164
+ spyEventType: 'DynamoDB',
165
+ eventName: record.eventName,
166
+ newImage: unmarshall(record.dynamodb?.NewImage as any),
167
+ keys: unmarshall(record.dynamodb?.Keys as any),
168
+ oldImage: record.dynamodb?.OldImage
169
+ ? unmarshall(record.dynamodb?.OldImage as any)
170
+ : undefined,
171
+ };
172
+
173
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
174
+ data,
175
+ serviceKey: mapping[arn],
176
+ };
177
+ postDataPromises.push(postData(fluentEvent));
178
+ }
179
+ } else if (
180
+ event.detail &&
181
+ event['detail-type'] &&
182
+ event.version &&
183
+ event.source
184
+ ) {
185
+ console.log('*** EventBridge ***');
186
+ const eventEb = event as EventBridgeEvent<any, any>;
187
+
188
+ const serviceKey = mapping.eventBridge; // the is new lambda for each subscription
189
+
190
+ const spyEventType = getSpyEventType(serviceKey) as
191
+ | 'EventBridge'
192
+ | 'EventBridgeRule';
193
+
194
+ const message = eventEb.detail;
195
+
196
+ const data: EventBridgeSpyEvent | EventBridgeRuleSpyEvent = {
197
+ spyEventType,
198
+ detail: message,
199
+ detailType: eventEb['detail-type'],
200
+ source: eventEb.source,
201
+ time: eventEb.time,
202
+ account: eventEb.account,
203
+ };
204
+
205
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = {
206
+ data,
207
+ serviceKey,
208
+ };
209
+ postDataPromises.push(postData(fluentEvent));
210
+ } else {
211
+ console.log('*** OTHER ***');
212
+ const fluentEvent: Omit<SpyMessage, 'timestamp'> = event;
213
+ postDataPromises.push(postData(fluentEvent));
214
+ }
215
+
216
+ await Promise.all(postDataPromises);
217
+ }
218
+
219
+ export async function postData(spyMessage: Omit<SpyMessage, 'timestamp'>) {
220
+ // const postData = JSON.parse(event.body!).data;
221
+ console.log('postData', JSON.stringify(spyMessage));
222
+
223
+ if (!connections) {
224
+ return;
225
+ }
226
+
227
+ const postCalls = connections.map(async ({ connectionId }) => {
228
+ console.log(`Sending message to: ${connectionId.S}`);
229
+
230
+ try {
231
+ const postToConnectionCommand = new PostToConnectionCommand({
232
+ ConnectionId: connectionId.S,
233
+ Data: JSON.stringify({
234
+ timestamp: new Date().toISOString(),
235
+ serviceKey: spyMessage.serviceKey,
236
+ data: spyMessage.data,
237
+ }) as any,
238
+ });
239
+
240
+ await apigwManagementApi.send(postToConnectionCommand);
241
+ } catch (e) {
242
+ console.error(`Fails sending message to: ${connectionId.S}`, e);
243
+ console.error(`Status code: ${(e as any).statusCode}`);
244
+ if ((e as any).$metadata.httpStatusCode === 410) {
245
+ console.log(`Found stale connection, deleting ${connectionId}`);
246
+
247
+ const deleteParams = new DeleteItemCommand({
248
+ TableName: process.env[envVariableNames.TABLE_NAME],
249
+ Key: { connectionId },
250
+ });
251
+
252
+ await ddb.send(deleteParams);
253
+ } else {
254
+ throw e;
255
+ }
256
+ }
257
+ });
258
+
259
+ await Promise.all(postCalls);
260
+ console.log('Send message finish');
261
+ }
262
+
263
+ export function getSpyEventType(serviceKey: string) {
264
+ if (!serviceKey) {
265
+ throw new Error('Missing serviceKey');
266
+ }
267
+
268
+ return serviceKey.substring(0, serviceKey.indexOf('#'));
269
+ }
@@ -1 +1 @@
1
- v0.0.32
1
+ v0.0.34
@@ -1,14 +1,18 @@
1
1
  import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
2
2
  import { Callback, Context, Handler } from 'aws-lambda';
3
+ import { publishSpyEvent } from '../common/publishSpyEvent';
3
4
  import { FunctionContext } from '../common/spyEvents/FunctionContext';
4
5
  import { FunctionErrorSpyEvent } from '../common/spyEvents/FunctionErrorSpyEvent';
5
6
  import { FunctionRequestSpyEvent } from '../common/spyEvents/FunctionRequestSpyEvent';
6
7
  import { FunctionResponseSpyEvent } from '../common/spyEvents/FunctionResponseSpyEvent';
8
+ import { envVariableNames } from '../src/common/envVariableNames';
7
9
  import { load } from './aws/UserFunction';
8
10
 
9
11
  const ORIGINAL_HANDLER_KEY = 'ORIGINAL_HANDLER';
10
- const fluentTestSendFunctionName = process.env.FLUENT_TEST_SEND_FUNCTION_NAME;
11
- const subscribedToSQS = process.env.FLUENT_TEST_SUBSCRIBED_TO_SQS === 'true';
12
+ const fluentTestSendFunctionName =
13
+ process.env[envVariableNames.FLUENT_TEST_SEND_FUNCTION_NAME];
14
+ const subscribedToSQS =
15
+ process.env[envVariableNames.FLUENT_TEST_SUBSCRIBED_TO_SQS] === 'true';
12
16
 
13
17
  const lambdaClient = new LambdaClient({});
14
18
 
@@ -37,7 +41,7 @@ export const handler = (
37
41
  };
38
42
 
39
43
  console.log('EXTENSION REQUEST:', JSON.stringify(event));
40
- const key = `Function#${process.env.FUNCTION_NAME}#Request`;
44
+ const key = `Function#${process.env[envVariableNames.FUNCTION_NAME]}#Request`;
41
45
  const p = sendLambdaSpyEvent(key, <FunctionRequestSpyEvent>{
42
46
  request: event,
43
47
  context: contextSpy,
@@ -47,7 +51,8 @@ export const handler = (
47
51
  const originalHandler = getOriginalHandler();
48
52
 
49
53
  const fail = (error: any) => {
50
- const key = `Function#${process.env.FUNCTION_NAME}#Error`;
54
+ console.error('FAIL', error);
55
+ const key = `Function#${process.env[envVariableNames.FUNCTION_NAME]}#Error`;
51
56
  const p = sendLambdaSpyEvent(key, <FunctionErrorSpyEvent>{
52
57
  request: event,
53
58
  error,
@@ -59,7 +64,9 @@ export const handler = (
59
64
 
60
65
  const succeed = (response: any) => {
61
66
  console.log('EXTENSION RESPONSE:', JSON.stringify(response));
62
- const key = `Function#${process.env.FUNCTION_NAME}#Response`;
67
+ const key = `Function#${
68
+ process.env[envVariableNames[envVariableNames.FUNCTION_NAME]]
69
+ }#Response`;
63
70
  const p = sendLambdaSpyEvent(key, <FunctionResponseSpyEvent>{
64
71
  request: event,
65
72
  response,
@@ -71,7 +78,7 @@ export const handler = (
71
78
 
72
79
  const newCallback = (err: any, data: any) => {
73
80
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
74
- (err ? fail(data) : succeed(data)).then(() => {
81
+ (err ? fail(err) : succeed(data)).then(() => {
75
82
  callback(err, data);
76
83
  });
77
84
  };
@@ -125,13 +132,17 @@ async function sendLambdaSpyEvent(
125
132
  }
126
133
 
127
134
  async function sendRawSpyEvent(data: any) {
128
- const command = new InvokeCommand({
129
- FunctionName: fluentTestSendFunctionName,
130
- InvocationType: 'RequestResponse',
131
- LogType: 'Tail',
132
- Payload: JSON.stringify(data) as any,
133
- });
134
- await lambdaClient.send(command);
135
+ await publishSpyEvent(data);
136
+
137
+ // console.log('EXTENSION SPY EVENT:', JSON.stringify(data));
138
+
139
+ // const command = new InvokeCommand({
140
+ // FunctionName: fluentTestSendFunctionName,
141
+ // InvocationType: 'RequestResponse',
142
+ // LogType: 'Tail',
143
+ // Payload: JSON.stringify(data) as any,
144
+ // });
145
+ // await lambdaClient.send(command);
135
146
  }
136
147
 
137
148
  function getOriginalHandler(): Handler {
@@ -1,5 +1,6 @@
1
1
  import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
2
2
  import { APIGatewayEvent } from 'aws-lambda';
3
+ import { envVariableNames } from '../src/common/envVariableNames';
3
4
 
4
5
  const ddb = new DynamoDBClient({
5
6
  region: process.env.AWS_REGION,
@@ -9,7 +10,7 @@ exports.handler = async (event: APIGatewayEvent) => {
9
10
  console.log('EVENT', JSON.stringify(event));
10
11
 
11
12
  const putParams = new PutItemCommand({
12
- TableName: process.env.TABLE_NAME as string,
13
+ TableName: process.env[envVariableNames.TABLE_NAME] as string,
13
14
  Item: {
14
15
  connectionId: { S: event.requestContext.connectionId! },
15
16
  },
@@ -1,5 +1,6 @@
1
1
  import { DeleteItemCommand, DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
2
  import { APIGatewayEvent } from 'aws-lambda';
3
+ import { envVariableNames } from '../src/common/envVariableNames';
3
4
 
4
5
  const ddb = new DynamoDBClient({
5
6
  region: process.env.AWS_REGION,
@@ -8,7 +9,7 @@ export const handler = async (event: APIGatewayEvent) => {
8
9
  console.log('EVENT', JSON.stringify(event));
9
10
 
10
11
  const deleteParams = new DeleteItemCommand({
11
- TableName: process.env.TABLE_NAME,
12
+ TableName: process.env[envVariableNames.TABLE_NAME],
12
13
  Key: {
13
14
  connectionId: { S: event.requestContext.connectionId! },
14
15
  },