@onivoro/server-aws-sns 24.30.12 → 24.30.13
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/README.md +186 -748
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @onivoro/server-aws-sns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AWS SNS integration for NestJS applications.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,838 +8,276 @@ A NestJS module for integrating with AWS SNS (Simple Notification Service), prov
|
|
|
8
8
|
npm install @onivoro/server-aws-sns
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Overview
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- **Message Publishing**: Publish messages to SNS topics
|
|
15
|
-
- **Subscription Management**: Create and manage topic subscriptions
|
|
16
|
-
- **SMS Notifications**: Send SMS messages directly through SNS
|
|
17
|
-
- **Email Notifications**: Send email notifications via SNS
|
|
18
|
-
- **Mobile Push Notifications**: Support for mobile app push notifications
|
|
19
|
-
- **Topic Management**: Create, list, and manage SNS topics
|
|
20
|
-
- **Message Attributes**: Support for custom message attributes and filtering
|
|
21
|
-
- **Environment-Based Configuration**: Configurable SNS settings per environment
|
|
22
|
-
- **Credential Provider Integration**: Seamless integration with AWS credential providers
|
|
13
|
+
This library provides AWS SNS client injection for NestJS applications. It's a minimal wrapper that configures and provides access to the AWS SNS client.
|
|
23
14
|
|
|
24
|
-
##
|
|
25
|
-
|
|
26
|
-
### 1. Module Configuration
|
|
15
|
+
## Module Setup
|
|
27
16
|
|
|
28
17
|
```typescript
|
|
18
|
+
import { Module } from '@nestjs/common';
|
|
29
19
|
import { ServerAwsSnsModule } from '@onivoro/server-aws-sns';
|
|
30
20
|
|
|
31
21
|
@Module({
|
|
32
22
|
imports: [
|
|
33
|
-
ServerAwsSnsModule.configure(
|
|
34
|
-
|
|
35
|
-
AWS_PROFILE: process.env.AWS_PROFILE || 'default',
|
|
36
|
-
}),
|
|
37
|
-
],
|
|
23
|
+
ServerAwsSnsModule.configure()
|
|
24
|
+
]
|
|
38
25
|
})
|
|
39
26
|
export class AppModule {}
|
|
40
27
|
```
|
|
41
28
|
|
|
42
|
-
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
The module uses environment-based configuration:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export class ServerAwsSnsConfig {
|
|
35
|
+
AWS_REGION: string;
|
|
36
|
+
AWS_PROFILE?: string; // Optional AWS profile
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
This module provides only the SNS client injection. There is no custom service implementation. You need to inject the SNS client directly and use AWS SDK methods:
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
|
-
import {
|
|
45
|
+
import { Injectable } from '@nestjs/common';
|
|
46
|
+
import { SNSClient, PublishCommand, CreateTopicCommand, SubscribeCommand } from '@aws-sdk/client-sns';
|
|
46
47
|
|
|
47
48
|
@Injectable()
|
|
48
49
|
export class NotificationService {
|
|
49
|
-
constructor(private snsClient: SNSClient) {}
|
|
50
|
+
constructor(private readonly snsClient: SNSClient) {}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
// Publish message to topic
|
|
53
|
+
async publishToTopic(topicArn: string, message: string, subject?: string) {
|
|
54
|
+
const command = new PublishCommand({
|
|
53
55
|
TopicArn: topicArn,
|
|
54
56
|
Message: message,
|
|
55
57
|
Subject: subject
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
return this.snsClient.send(
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return await this.snsClient.send(command);
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
// Send SMS
|
|
61
64
|
async sendSMS(phoneNumber: string, message: string) {
|
|
62
|
-
const
|
|
65
|
+
const command = new PublishCommand({
|
|
63
66
|
PhoneNumber: phoneNumber,
|
|
64
67
|
Message: message
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
return this.snsClient.send(
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return await this.snsClient.send(command);
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
// Create topic
|
|
70
74
|
async createTopic(topicName: string) {
|
|
71
|
-
const
|
|
75
|
+
const command = new CreateTopicCommand({
|
|
72
76
|
Name: topicName
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Configuration
|
|
81
|
-
|
|
82
|
-
### ServerAwsSnsConfig
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import { ServerAwsSnsConfig } from '@onivoro/server-aws-sns';
|
|
86
|
-
|
|
87
|
-
export class AppSnsConfig extends ServerAwsSnsConfig {
|
|
88
|
-
AWS_REGION = process.env.AWS_REGION || 'us-east-1';
|
|
89
|
-
AWS_PROFILE = process.env.AWS_PROFILE || 'default';
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Environment Variables
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
# AWS Configuration
|
|
97
|
-
AWS_REGION=us-east-1
|
|
98
|
-
AWS_PROFILE=default
|
|
99
|
-
|
|
100
|
-
# Optional SNS Configuration
|
|
101
|
-
SNS_DEFAULT_TOPIC_ARN=arn:aws:sns:us-east-1:123456789012:my-topic
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Usage Examples
|
|
105
|
-
|
|
106
|
-
### Topic Management Service
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
import {
|
|
110
|
-
SNSClient,
|
|
111
|
-
CreateTopicCommand,
|
|
112
|
-
DeleteTopicCommand,
|
|
113
|
-
ListTopicsCommand,
|
|
114
|
-
GetTopicAttributesCommand,
|
|
115
|
-
SetTopicAttributesCommand
|
|
116
|
-
} from '@aws-sdk/client-sns';
|
|
117
|
-
|
|
118
|
-
@Injectable()
|
|
119
|
-
export class SnsTopicService {
|
|
120
|
-
constructor(private snsClient: SNSClient) {}
|
|
121
|
-
|
|
122
|
-
async createTopic(topicName: string, displayName?: string) {
|
|
123
|
-
const createParams = {
|
|
124
|
-
Name: topicName,
|
|
125
|
-
Attributes: displayName ? { DisplayName: displayName } : undefined
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const result = await this.snsClient.send(new CreateTopicCommand(createParams));
|
|
129
|
-
return result.TopicArn;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async deleteTopic(topicArn: string) {
|
|
133
|
-
const params = {
|
|
134
|
-
TopicArn: topicArn
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return this.snsClient.send(new DeleteTopicCommand(params));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async listTopics() {
|
|
141
|
-
return this.snsClient.send(new ListTopicsCommand({}));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async getTopicAttributes(topicArn: string) {
|
|
145
|
-
const params = {
|
|
146
|
-
TopicArn: topicArn
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
return this.snsClient.send(new GetTopicAttributesCommand(params));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const response = await this.snsClient.send(command);
|
|
80
|
+
return response.TopicArn;
|
|
150
81
|
}
|
|
151
82
|
|
|
152
|
-
|
|
153
|
-
|
|
83
|
+
// Subscribe to topic
|
|
84
|
+
async subscribeEmail(topicArn: string, email: string) {
|
|
85
|
+
const command = new SubscribeCommand({
|
|
154
86
|
TopicArn: topicArn,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return this.snsClient.send(
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async enableDeliveryStatusLogging(topicArn: string, roleArn: string) {
|
|
163
|
-
const attributes = {
|
|
164
|
-
'DeliveryStatusSuccessSamplingRate': '100',
|
|
165
|
-
'DeliveryStatusFailureSamplingRate': '100',
|
|
166
|
-
'DeliveryStatusLogging': 'true',
|
|
167
|
-
'DeliveryStatusLogSuccessFeedbackRoleArn': roleArn,
|
|
168
|
-
'DeliveryStatusLogFailureFeedbackRoleArn': roleArn
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const promises = Object.entries(attributes).map(([AttributeName, AttributeValue]) =>
|
|
172
|
-
this.snsClient.send(new SetTopicAttributesCommand({
|
|
173
|
-
TopicArn: topicArn,
|
|
174
|
-
AttributeName,
|
|
175
|
-
AttributeValue
|
|
176
|
-
}))
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
return Promise.all(promises);
|
|
87
|
+
Protocol: 'email',
|
|
88
|
+
Endpoint: email
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return await this.snsClient.send(command);
|
|
180
92
|
}
|
|
181
93
|
}
|
|
182
94
|
```
|
|
183
95
|
|
|
184
|
-
|
|
96
|
+
## Complete Example
|
|
185
97
|
|
|
186
98
|
```typescript
|
|
99
|
+
import { Module, Injectable, Controller, Post, Body } from '@nestjs/common';
|
|
100
|
+
import { ServerAwsSnsModule } from '@onivoro/server-aws-sns';
|
|
187
101
|
import {
|
|
188
102
|
SNSClient,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
103
|
+
PublishCommand,
|
|
104
|
+
CreateTopicCommand,
|
|
105
|
+
ListTopicsCommand,
|
|
106
|
+
DeleteTopicCommand
|
|
193
107
|
} from '@aws-sdk/client-sns';
|
|
194
108
|
|
|
195
|
-
@
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
TopicArn: topicArn,
|
|
202
|
-
Protocol: 'email',
|
|
203
|
-
Endpoint: emailAddress
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
return this.snsClient.send(new SubscribeCommand(params));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async subscribeSMS(topicArn: string, phoneNumber: string) {
|
|
210
|
-
const params = {
|
|
211
|
-
TopicArn: topicArn,
|
|
212
|
-
Protocol: 'sms',
|
|
213
|
-
Endpoint: phoneNumber
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
return this.snsClient.send(new SubscribeCommand(params));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async subscribeHTTP(topicArn: string, httpEndpoint: string) {
|
|
220
|
-
const params = {
|
|
221
|
-
TopicArn: topicArn,
|
|
222
|
-
Protocol: 'http',
|
|
223
|
-
Endpoint: httpEndpoint
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
return this.snsClient.send(new SubscribeCommand(params));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async subscribeHTTPS(topicArn: string, httpsEndpoint: string) {
|
|
230
|
-
const params = {
|
|
231
|
-
TopicArn: topicArn,
|
|
232
|
-
Protocol: 'https',
|
|
233
|
-
Endpoint: httpsEndpoint
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
return this.snsClient.send(new SubscribeCommand(params));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async subscribeLambda(topicArn: string, lambdaArn: string) {
|
|
240
|
-
const params = {
|
|
241
|
-
TopicArn: topicArn,
|
|
242
|
-
Protocol: 'lambda',
|
|
243
|
-
Endpoint: lambdaArn
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
return this.snsClient.send(new SubscribeCommand(params));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async subscribeSQS(topicArn: string, sqsQueueArn: string) {
|
|
250
|
-
const params = {
|
|
251
|
-
TopicArn: topicArn,
|
|
252
|
-
Protocol: 'sqs',
|
|
253
|
-
Endpoint: sqsQueueArn
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
return this.snsClient.send(new SubscribeCommand(params));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async unsubscribe(subscriptionArn: string) {
|
|
260
|
-
const params = {
|
|
261
|
-
SubscriptionArn: subscriptionArn
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
return this.snsClient.send(new UnsubscribeCommand(params));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async listSubscriptions(topicArn: string) {
|
|
268
|
-
const params = {
|
|
269
|
-
TopicArn: topicArn
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
return this.snsClient.send(new ListSubscriptionsByTopicCommand(params));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async confirmSubscription(topicArn: string, token: string) {
|
|
276
|
-
const params = {
|
|
277
|
-
TopicArn: topicArn,
|
|
278
|
-
Token: token
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
return this.snsClient.send(new ConfirmSubscriptionCommand(params));
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Message Publishing Service
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
import { SNSClient, PublishCommand, PublishBatchCommand } from '@aws-sdk/client-sns';
|
|
290
|
-
|
|
291
|
-
interface MessageAttributes {
|
|
292
|
-
[key: string]: {
|
|
293
|
-
DataType: 'String' | 'Number' | 'Binary';
|
|
294
|
-
StringValue?: string;
|
|
295
|
-
BinaryValue?: Uint8Array;
|
|
296
|
-
};
|
|
297
|
-
}
|
|
109
|
+
@Module({
|
|
110
|
+
imports: [ServerAwsSnsModule.configure()],
|
|
111
|
+
controllers: [NotificationController],
|
|
112
|
+
providers: [NotificationService]
|
|
113
|
+
})
|
|
114
|
+
export class NotificationModule {}
|
|
298
115
|
|
|
299
116
|
@Injectable()
|
|
300
|
-
export class
|
|
301
|
-
constructor(private snsClient: SNSClient) {}
|
|
302
|
-
|
|
303
|
-
async
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
async publishStructuredMessage(
|
|
320
|
-
topicArn: string,
|
|
321
|
-
message: any,
|
|
322
|
-
subject?: string,
|
|
323
|
-
messageAttributes?: MessageAttributes
|
|
324
|
-
) {
|
|
325
|
-
const messagePayload = {
|
|
326
|
-
default: JSON.stringify(message),
|
|
327
|
-
email: JSON.stringify(message),
|
|
328
|
-
sms: typeof message === 'string' ? message : JSON.stringify(message)
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const params = {
|
|
332
|
-
TopicArn: topicArn,
|
|
333
|
-
Message: JSON.stringify(messagePayload),
|
|
334
|
-
Subject: subject,
|
|
117
|
+
export class NotificationService {
|
|
118
|
+
constructor(private readonly snsClient: SNSClient) {}
|
|
119
|
+
|
|
120
|
+
async sendNotification(type: string, message: string, recipients: string[]) {
|
|
121
|
+
const topicName = `notifications-${type}`;
|
|
122
|
+
|
|
123
|
+
// Create or get topic
|
|
124
|
+
const createCommand = new CreateTopicCommand({ Name: topicName });
|
|
125
|
+
const { TopicArn } = await this.snsClient.send(createCommand);
|
|
126
|
+
|
|
127
|
+
// Publish message
|
|
128
|
+
const publishCommand = new PublishCommand({
|
|
129
|
+
TopicArn,
|
|
130
|
+
Message: JSON.stringify({
|
|
131
|
+
default: message,
|
|
132
|
+
email: message,
|
|
133
|
+
sms: message.substring(0, 140) // SMS has character limit
|
|
134
|
+
}),
|
|
335
135
|
MessageStructure: 'json',
|
|
336
|
-
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async publishToPhone(phoneNumber: string, message: string, messageAttributes?: MessageAttributes) {
|
|
343
|
-
const params = {
|
|
344
|
-
PhoneNumber: phoneNumber,
|
|
345
|
-
Message: message,
|
|
346
|
-
MessageAttributes: messageAttributes
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async publishBatch(topicArn: string, messages: Array<{
|
|
353
|
-
Id: string;
|
|
354
|
-
Message: string;
|
|
355
|
-
Subject?: string;
|
|
356
|
-
MessageAttributes?: MessageAttributes;
|
|
357
|
-
}>) {
|
|
358
|
-
const params = {
|
|
359
|
-
TopicArn: topicArn,
|
|
360
|
-
PublishRequestEntries: messages
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
return this.snsClient.send(new PublishBatchCommand(params));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
async publishWithDeduplication(
|
|
367
|
-
topicArn: string,
|
|
368
|
-
message: string,
|
|
369
|
-
messageGroupId: string,
|
|
370
|
-
messageDeduplicationId: string,
|
|
371
|
-
subject?: string
|
|
372
|
-
) {
|
|
373
|
-
const params = {
|
|
374
|
-
TopicArn: topicArn,
|
|
375
|
-
Message: message,
|
|
376
|
-
Subject: subject,
|
|
377
|
-
MessageGroupId: messageGroupId,
|
|
378
|
-
MessageDeduplicationId: messageDeduplicationId
|
|
379
|
-
};
|
|
136
|
+
Subject: `${type} Notification`
|
|
137
|
+
});
|
|
380
138
|
|
|
381
|
-
return this.snsClient.send(
|
|
139
|
+
return await this.snsClient.send(publishCommand);
|
|
382
140
|
}
|
|
383
141
|
|
|
384
|
-
async
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
) {
|
|
393
|
-
const messageAttributes: MessageAttributes = {
|
|
394
|
-
'notification.title': {
|
|
395
|
-
DataType: 'String',
|
|
396
|
-
StringValue: notification.title
|
|
397
|
-
},
|
|
398
|
-
'notification.body': {
|
|
399
|
-
DataType: 'String',
|
|
400
|
-
StringValue: notification.body
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
if (notification.priority) {
|
|
405
|
-
messageAttributes['notification.priority'] = {
|
|
406
|
-
DataType: 'String',
|
|
407
|
-
StringValue: notification.priority
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return this.publishMessage(
|
|
412
|
-
topicArn,
|
|
413
|
-
JSON.stringify(notification),
|
|
414
|
-
notification.title,
|
|
415
|
-
messageAttributes
|
|
142
|
+
async sendBulkSMS(phoneNumbers: string[], message: string) {
|
|
143
|
+
const results = await Promise.allSettled(
|
|
144
|
+
phoneNumbers.map(phone =>
|
|
145
|
+
this.snsClient.send(new PublishCommand({
|
|
146
|
+
PhoneNumber: phone,
|
|
147
|
+
Message: message
|
|
148
|
+
}))
|
|
149
|
+
)
|
|
416
150
|
);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Mobile Push Notification Service
|
|
422
|
-
|
|
423
|
-
```typescript
|
|
424
|
-
import { SNSClient, CreatePlatformApplicationCommand, CreatePlatformEndpointCommand, PublishCommand } from '@aws-sdk/client-sns';
|
|
425
|
-
|
|
426
|
-
@Injectable()
|
|
427
|
-
export class SnsMobilePushService {
|
|
428
|
-
constructor(private snsClient: SNSClient) {}
|
|
429
|
-
|
|
430
|
-
async createPlatformApplication(
|
|
431
|
-
name: string,
|
|
432
|
-
platform: 'GCM' | 'APNS' | 'APNS_SANDBOX',
|
|
433
|
-
credentials: { [key: string]: string }
|
|
434
|
-
) {
|
|
435
|
-
const params = {
|
|
436
|
-
Name: name,
|
|
437
|
-
Platform: platform,
|
|
438
|
-
Attributes: credentials
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
return this.snsClient.send(new CreatePlatformApplicationCommand(params));
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async createPlatformEndpoint(
|
|
445
|
-
platformApplicationArn: string,
|
|
446
|
-
token: string,
|
|
447
|
-
customUserData?: string
|
|
448
|
-
) {
|
|
449
|
-
const params = {
|
|
450
|
-
PlatformApplicationArn: platformApplicationArn,
|
|
451
|
-
Token: token,
|
|
452
|
-
CustomUserData: customUserData
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
return this.snsClient.send(new CreatePlatformEndpointCommand(params));
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
async sendPushNotification(
|
|
459
|
-
targetArn: string,
|
|
460
|
-
message: string,
|
|
461
|
-
badge?: number,
|
|
462
|
-
sound?: string
|
|
463
|
-
) {
|
|
464
|
-
const gcmPayload = {
|
|
465
|
-
data: {
|
|
466
|
-
message: message
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
151
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
badge: badge,
|
|
474
|
-
sound: sound || 'default'
|
|
475
|
-
}
|
|
152
|
+
return {
|
|
153
|
+
successful: results.filter(r => r.status === 'fulfilled').length,
|
|
154
|
+
failed: results.filter(r => r.status === 'rejected').length
|
|
476
155
|
};
|
|
477
|
-
|
|
478
|
-
const messagePayload = {
|
|
479
|
-
default: message,
|
|
480
|
-
GCM: JSON.stringify(gcmPayload),
|
|
481
|
-
APNS: JSON.stringify(apnsPayload),
|
|
482
|
-
APNS_SANDBOX: JSON.stringify(apnsPayload)
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
const params = {
|
|
486
|
-
TargetArn: targetArn,
|
|
487
|
-
Message: JSON.stringify(messagePayload),
|
|
488
|
-
MessageStructure: 'json'
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
492
156
|
}
|
|
493
157
|
|
|
494
|
-
async
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const apnsPayload = {
|
|
500
|
-
aps: {
|
|
501
|
-
'content-available': 1
|
|
502
|
-
},
|
|
503
|
-
data: data
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
const messagePayload = {
|
|
507
|
-
GCM: JSON.stringify(gcmPayload),
|
|
508
|
-
APNS: JSON.stringify(apnsPayload),
|
|
509
|
-
APNS_SANDBOX: JSON.stringify(apnsPayload)
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
const params = {
|
|
513
|
-
TargetArn: targetArn,
|
|
514
|
-
Message: JSON.stringify(messagePayload),
|
|
515
|
-
MessageStructure: 'json'
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
158
|
+
async listTopics() {
|
|
159
|
+
const command = new ListTopicsCommand({});
|
|
160
|
+
const response = await this.snsClient.send(command);
|
|
161
|
+
return response.Topics;
|
|
519
162
|
}
|
|
520
163
|
}
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
### SMS Service
|
|
524
|
-
|
|
525
|
-
```typescript
|
|
526
|
-
import { SNSClient, PublishCommand, SetSMSAttributesCommand, GetSMSAttributesCommand } from '@aws-sdk/client-sns';
|
|
527
164
|
|
|
528
|
-
@
|
|
529
|
-
export class
|
|
530
|
-
constructor(private
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
DataType: 'String',
|
|
543
|
-
StringValue: senderName
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (messageType) {
|
|
548
|
-
messageAttributes['AWS.SNS.SMS.SMSType'] = {
|
|
549
|
-
DataType: 'String',
|
|
550
|
-
StringValue: messageType
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const params = {
|
|
555
|
-
PhoneNumber: phoneNumber,
|
|
556
|
-
Message: message,
|
|
557
|
-
MessageAttributes: Object.keys(messageAttributes).length > 0 ? messageAttributes : undefined
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
async sendTransactionalSMS(phoneNumber: string, message: string, senderName?: string) {
|
|
564
|
-
return this.sendSMS(phoneNumber, message, senderName, 'Transactional');
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
async sendPromotionalSMS(phoneNumber: string, message: string, senderName?: string) {
|
|
568
|
-
return this.sendSMS(phoneNumber, message, senderName, 'Promotional');
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
async setSMSAttributes(attributes: { [key: string]: string }) {
|
|
572
|
-
const promises = Object.entries(attributes).map(([key, value]) =>
|
|
573
|
-
this.snsClient.send(new SetSMSAttributesCommand({
|
|
574
|
-
attributes: { [key]: value }
|
|
575
|
-
}))
|
|
165
|
+
@Controller('notifications')
|
|
166
|
+
export class NotificationController {
|
|
167
|
+
constructor(private readonly notificationService: NotificationService) {}
|
|
168
|
+
|
|
169
|
+
@Post('send')
|
|
170
|
+
async sendNotification(@Body() body: {
|
|
171
|
+
type: string;
|
|
172
|
+
message: string;
|
|
173
|
+
recipients: string[];
|
|
174
|
+
}) {
|
|
175
|
+
return await this.notificationService.sendNotification(
|
|
176
|
+
body.type,
|
|
177
|
+
body.message,
|
|
178
|
+
body.recipients
|
|
576
179
|
);
|
|
577
|
-
|
|
578
|
-
return Promise.all(promises);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
async getSMSAttributes() {
|
|
582
|
-
return this.snsClient.send(new GetSMSAttributesCommand({}));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
async setDefaultSenderName(senderName: string) {
|
|
586
|
-
return this.setSMSAttributes({
|
|
587
|
-
'DefaultSenderID': senderName
|
|
588
|
-
});
|
|
589
180
|
}
|
|
590
181
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
});
|
|
182
|
+
@Post('sms/bulk')
|
|
183
|
+
async sendBulkSMS(@Body() body: {
|
|
184
|
+
phoneNumbers: string[];
|
|
185
|
+
message: string;
|
|
186
|
+
}) {
|
|
187
|
+
return await this.notificationService.sendBulkSMS(
|
|
188
|
+
body.phoneNumbers,
|
|
189
|
+
body.message
|
|
190
|
+
);
|
|
601
191
|
}
|
|
602
192
|
}
|
|
603
193
|
```
|
|
604
194
|
|
|
605
|
-
##
|
|
195
|
+
## Common SNS Operations
|
|
606
196
|
|
|
607
|
-
|
|
197
|
+
Since this module only provides the client, here are examples of common operations:
|
|
608
198
|
|
|
199
|
+
### Topic Management
|
|
609
200
|
```typescript
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
async subscribeWithFilter(
|
|
615
|
-
topicArn: string,
|
|
616
|
-
protocol: string,
|
|
617
|
-
endpoint: string,
|
|
618
|
-
filterPolicy: any
|
|
619
|
-
) {
|
|
620
|
-
const subscribeResult = await this.snsClient.send(new SubscribeCommand({
|
|
621
|
-
TopicArn: topicArn,
|
|
622
|
-
Protocol: protocol,
|
|
623
|
-
Endpoint: endpoint
|
|
624
|
-
}));
|
|
625
|
-
|
|
626
|
-
if (subscribeResult.SubscriptionArn && filterPolicy) {
|
|
627
|
-
await this.snsClient.send(new SetSubscriptionAttributesCommand({
|
|
628
|
-
SubscriptionArn: subscribeResult.SubscriptionArn,
|
|
629
|
-
AttributeName: 'FilterPolicy',
|
|
630
|
-
AttributeValue: JSON.stringify(filterPolicy)
|
|
631
|
-
}));
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return subscribeResult;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
async updateFilterPolicy(subscriptionArn: string, filterPolicy: any) {
|
|
638
|
-
const params = {
|
|
639
|
-
SubscriptionArn: subscriptionArn,
|
|
640
|
-
AttributeName: 'FilterPolicy',
|
|
641
|
-
AttributeValue: JSON.stringify(filterPolicy)
|
|
642
|
-
};
|
|
201
|
+
// Create topic
|
|
202
|
+
const createCommand = new CreateTopicCommand({ Name: 'my-topic' });
|
|
203
|
+
const { TopicArn } = await snsClient.send(createCommand);
|
|
643
204
|
|
|
644
|
-
|
|
645
|
-
|
|
205
|
+
// List topics
|
|
206
|
+
const listCommand = new ListTopicsCommand({});
|
|
207
|
+
const { Topics } = await snsClient.send(listCommand);
|
|
646
208
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
attributes: { [key: string]: string | number | boolean }
|
|
651
|
-
) {
|
|
652
|
-
const messageAttributes: any = {};
|
|
653
|
-
|
|
654
|
-
Object.entries(attributes).forEach(([key, value]) => {
|
|
655
|
-
messageAttributes[key] = {
|
|
656
|
-
DataType: typeof value === 'number' ? 'Number' : 'String',
|
|
657
|
-
StringValue: value.toString()
|
|
658
|
-
};
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
const params = {
|
|
662
|
-
TopicArn: topicArn,
|
|
663
|
-
Message: message,
|
|
664
|
-
MessageAttributes: messageAttributes
|
|
665
|
-
};
|
|
666
|
-
|
|
667
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
668
|
-
}
|
|
669
|
-
}
|
|
209
|
+
// Delete topic
|
|
210
|
+
const deleteCommand = new DeleteTopicCommand({ TopicArn });
|
|
211
|
+
await snsClient.send(deleteCommand);
|
|
670
212
|
```
|
|
671
213
|
|
|
672
|
-
###
|
|
673
|
-
|
|
214
|
+
### Subscriptions
|
|
674
215
|
```typescript
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
@Injectable()
|
|
684
|
-
export class SnsEventNotificationService {
|
|
685
|
-
constructor(private snsClient: SNSClient) {}
|
|
686
|
-
|
|
687
|
-
async publishEvent(topicArn: string, event: NotificationEvent) {
|
|
688
|
-
const messageAttributes = {
|
|
689
|
-
'event.type': {
|
|
690
|
-
DataType: 'String',
|
|
691
|
-
StringValue: event.type
|
|
692
|
-
},
|
|
693
|
-
'event.timestamp': {
|
|
694
|
-
DataType: 'String',
|
|
695
|
-
StringValue: event.timestamp
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
if (event.userId) {
|
|
700
|
-
messageAttributes['event.userId'] = {
|
|
701
|
-
DataType: 'String',
|
|
702
|
-
StringValue: event.userId
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
if (event.orderId) {
|
|
707
|
-
messageAttributes['event.orderId'] = {
|
|
708
|
-
DataType: 'String',
|
|
709
|
-
StringValue: event.orderId
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const params = {
|
|
714
|
-
TopicArn: topicArn,
|
|
715
|
-
Message: JSON.stringify(event),
|
|
716
|
-
Subject: `Event: ${event.type}`,
|
|
717
|
-
MessageAttributes: messageAttributes
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
return this.snsClient.send(new PublishCommand(params));
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
async publishUserSignupEvent(topicArn: string, userId: string, userData: any) {
|
|
724
|
-
const event: NotificationEvent = {
|
|
725
|
-
type: 'user.signup',
|
|
726
|
-
userId,
|
|
727
|
-
data: userData,
|
|
728
|
-
timestamp: new Date().toISOString()
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
return this.publishEvent(topicArn, event);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
async publishOrderEvent(topicArn: string, orderId: string, userId: string, orderData: any) {
|
|
735
|
-
const event: NotificationEvent = {
|
|
736
|
-
type: 'order.created',
|
|
737
|
-
userId,
|
|
738
|
-
orderId,
|
|
739
|
-
data: orderData,
|
|
740
|
-
timestamp: new Date().toISOString()
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
return this.publishEvent(topicArn, event);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
async publishSystemAlert(topicArn: string, alertData: any) {
|
|
747
|
-
const event: NotificationEvent = {
|
|
748
|
-
type: 'system.alert',
|
|
749
|
-
data: alertData,
|
|
750
|
-
timestamp: new Date().toISOString()
|
|
751
|
-
};
|
|
216
|
+
// Email subscription
|
|
217
|
+
const subscribeCommand = new SubscribeCommand({
|
|
218
|
+
TopicArn: 'arn:aws:sns:...',
|
|
219
|
+
Protocol: 'email',
|
|
220
|
+
Endpoint: 'user@example.com'
|
|
221
|
+
});
|
|
222
|
+
await snsClient.send(subscribeCommand);
|
|
752
223
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
224
|
+
// SMS subscription
|
|
225
|
+
const smsSubscribeCommand = new SubscribeCommand({
|
|
226
|
+
TopicArn: 'arn:aws:sns:...',
|
|
227
|
+
Protocol: 'sms',
|
|
228
|
+
Endpoint: '+1234567890'
|
|
229
|
+
});
|
|
230
|
+
await snsClient.send(smsSubscribeCommand);
|
|
756
231
|
```
|
|
757
232
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
### 1. Error Handling
|
|
761
|
-
|
|
233
|
+
### Publishing
|
|
762
234
|
```typescript
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
|
|
235
|
+
// Simple message
|
|
236
|
+
const publishCommand = new PublishCommand({
|
|
237
|
+
TopicArn: 'arn:aws:sns:...',
|
|
238
|
+
Message: 'Hello World'
|
|
239
|
+
});
|
|
240
|
+
await snsClient.send(publishCommand);
|
|
241
|
+
|
|
242
|
+
// Structured message for multiple protocols
|
|
243
|
+
const structuredCommand = new PublishCommand({
|
|
244
|
+
TopicArn: 'arn:aws:sns:...',
|
|
245
|
+
Message: JSON.stringify({
|
|
246
|
+
default: 'Default message',
|
|
247
|
+
email: 'Detailed email message',
|
|
248
|
+
sms: 'Short SMS'
|
|
249
|
+
}),
|
|
250
|
+
MessageStructure: 'json'
|
|
251
|
+
});
|
|
252
|
+
await snsClient.send(structuredCommand);
|
|
779
253
|
```
|
|
780
254
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
validateMessageSize(message: string): boolean {
|
|
785
|
-
// SNS has a 256KB limit for messages
|
|
786
|
-
const sizeInBytes = Buffer.byteLength(message, 'utf8');
|
|
787
|
-
const maxSize = 256 * 1024; // 256KB
|
|
788
|
-
|
|
789
|
-
if (sizeInBytes > maxSize) {
|
|
790
|
-
throw new Error(`Message size (${sizeInBytes} bytes) exceeds SNS limit (${maxSize} bytes)`);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
return true;
|
|
794
|
-
}
|
|
795
|
-
```
|
|
255
|
+
## Environment Variables
|
|
796
256
|
|
|
797
|
-
|
|
257
|
+
```bash
|
|
258
|
+
# Required
|
|
259
|
+
AWS_REGION=us-east-1
|
|
798
260
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
// E.164 format validation
|
|
802
|
-
const e164Regex = /^\+[1-9]\d{1,14}$/;
|
|
803
|
-
return e164Regex.test(phoneNumber);
|
|
804
|
-
}
|
|
261
|
+
# Optional
|
|
262
|
+
AWS_PROFILE=my-profile
|
|
805
263
|
```
|
|
806
264
|
|
|
807
|
-
##
|
|
265
|
+
## Limitations
|
|
808
266
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
267
|
+
- No custom service implementation - only provides SNS client
|
|
268
|
+
- No built-in error handling or retry logic
|
|
269
|
+
- No message formatting utilities
|
|
270
|
+
- No subscription management helpers
|
|
271
|
+
- Must use AWS SDK methods directly
|
|
813
272
|
|
|
814
|
-
|
|
815
|
-
let snsClient: SNSClient;
|
|
816
|
-
|
|
817
|
-
beforeEach(async () => {
|
|
818
|
-
const module: TestingModule = await Test.createTestingModule({
|
|
819
|
-
imports: [ServerAwsSnsModule.configure({
|
|
820
|
-
AWS_REGION: 'us-east-1',
|
|
821
|
-
AWS_PROFILE: 'test'
|
|
822
|
-
})],
|
|
823
|
-
}).compile();
|
|
824
|
-
|
|
825
|
-
snsClient = module.get<SNSClient>(SNSClient);
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
it('should be defined', () => {
|
|
829
|
-
expect(snsClient).toBeDefined();
|
|
830
|
-
});
|
|
831
|
-
});
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
## API Reference
|
|
835
|
-
|
|
836
|
-
### Exported Classes
|
|
837
|
-
- `ServerAwsSnsConfig`: Configuration class for SNS settings
|
|
838
|
-
- `ServerAwsSnsModule`: NestJS module for SNS integration
|
|
273
|
+
## Best Practices
|
|
839
274
|
|
|
840
|
-
|
|
841
|
-
|
|
275
|
+
1. **Error Handling**: Implement proper error handling for SNS operations
|
|
276
|
+
2. **Message Size**: Keep messages under 256 KB
|
|
277
|
+
3. **Phone Numbers**: Validate phone numbers in E.164 format
|
|
278
|
+
4. **Topics**: Use meaningful topic names and manage lifecycle
|
|
279
|
+
5. **Permissions**: Ensure proper IAM permissions for SNS operations
|
|
842
280
|
|
|
843
281
|
## License
|
|
844
282
|
|
|
845
|
-
|
|
283
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onivoro/server-aws-sns",
|
|
3
|
-
"version": "24.30.
|
|
3
|
+
"version": "24.30.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"url": "https://github.com/onivoro/monorepo.git"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@onivoro/server-aws-credential-providers": "24.30.
|
|
14
|
-
"@onivoro/server-common": "24.30.
|
|
13
|
+
"@onivoro/server-aws-credential-providers": "24.30.13",
|
|
14
|
+
"@onivoro/server-common": "24.30.13",
|
|
15
15
|
"tslib": "^2.3.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|