@onivoro/server-aws-sns 24.30.12 → 24.30.14

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 (2) hide show
  1. package/README.md +186 -748
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @onivoro/server-aws-sns
2
2
 
3
- A NestJS module for integrating with AWS SNS (Simple Notification Service), providing message publishing, subscription management, and notification delivery capabilities for your server applications.
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
- ## Features
11
+ ## Overview
12
12
 
13
- - **SNS Client Integration**: Direct integration with AWS SNS service
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
- ## Quick Start
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
- AWS_REGION: 'us-east-1',
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
- ### 2. Basic Usage
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 { SNSClient, PublishCommand, CreateTopicCommand } from '@aws-sdk/client-sns';
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
- async publishMessage(topicArn: string, message: string, subject?: string) {
52
- const params = {
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(new PublishCommand(params));
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 params = {
65
+ const command = new PublishCommand({
63
66
  PhoneNumber: phoneNumber,
64
67
  Message: message
65
- };
66
-
67
- return this.snsClient.send(new PublishCommand(params));
68
+ });
69
+
70
+ return await this.snsClient.send(command);
68
71
  }
69
72
 
73
+ // Create topic
70
74
  async createTopic(topicName: string) {
71
- const params = {
75
+ const command = new CreateTopicCommand({
72
76
  Name: topicName
73
- };
74
-
75
- return this.snsClient.send(new CreateTopicCommand(params));
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
- async setTopicDisplayName(topicArn: string, displayName: string) {
153
- const params = {
83
+ // Subscribe to topic
84
+ async subscribeEmail(topicArn: string, email: string) {
85
+ const command = new SubscribeCommand({
154
86
  TopicArn: topicArn,
155
- AttributeName: 'DisplayName',
156
- AttributeValue: displayName
157
- };
158
-
159
- return this.snsClient.send(new SetTopicAttributesCommand(params));
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
- ### Subscription Management Service
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
- SubscribeCommand,
190
- UnsubscribeCommand,
191
- ListSubscriptionsByTopicCommand,
192
- ConfirmSubscriptionCommand
103
+ PublishCommand,
104
+ CreateTopicCommand,
105
+ ListTopicsCommand,
106
+ DeleteTopicCommand
193
107
  } from '@aws-sdk/client-sns';
194
108
 
195
- @Injectable()
196
- export class SnsSubscriptionService {
197
- constructor(private snsClient: SNSClient) {}
198
-
199
- async subscribeEmail(topicArn: string, emailAddress: string) {
200
- const params = {
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 SnsPublishService {
301
- constructor(private snsClient: SNSClient) {}
302
-
303
- async publishMessage(
304
- topicArn: string,
305
- message: string,
306
- subject?: string,
307
- messageAttributes?: MessageAttributes
308
- ) {
309
- const params = {
310
- TopicArn: topicArn,
311
- Message: message,
312
- Subject: subject,
313
- MessageAttributes: messageAttributes
314
- };
315
-
316
- return this.snsClient.send(new PublishCommand(params));
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
- MessageAttributes: messageAttributes
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(new PublishCommand(params));
139
+ return await this.snsClient.send(publishCommand);
382
140
  }
383
141
 
384
- async publishNotification(
385
- topicArn: string,
386
- notification: {
387
- title: string;
388
- body: string;
389
- data?: any;
390
- priority?: 'normal' | 'high';
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
- const apnsPayload = {
471
- aps: {
472
- alert: message,
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 sendSilentPushNotification(targetArn: string, data: any) {
495
- const gcmPayload = {
496
- data: data
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
- @Injectable()
529
- export class SnsSmsService {
530
- constructor(private snsClient: SNSClient) {}
531
-
532
- async sendSMS(
533
- phoneNumber: string,
534
- message: string,
535
- senderName?: string,
536
- messageType?: 'Promotional' | 'Transactional'
537
- ) {
538
- const messageAttributes: any = {};
539
-
540
- if (senderName) {
541
- messageAttributes['AWS.SNS.SMS.SenderID'] = {
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
- async setDefaultSMSType(smsType: 'Promotional' | 'Transactional') {
592
- return this.setSMSAttributes({
593
- 'DefaultSMSType': smsType
594
- });
595
- }
596
-
597
- async setMonthlySpendLimit(limitUSD: string) {
598
- return this.setSMSAttributes({
599
- 'MonthlySpendLimit': limitUSD
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
- ## Advanced Usage
195
+ ## Common SNS Operations
606
196
 
607
- ### Message Filtering Service
197
+ Since this module only provides the client, here are examples of common operations:
608
198
 
199
+ ### Topic Management
609
200
  ```typescript
610
- @Injectable()
611
- export class SnsFilteringService {
612
- constructor(private snsClient: SNSClient) {}
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
- return this.snsClient.send(new SetSubscriptionAttributesCommand(params));
645
- }
205
+ // List topics
206
+ const listCommand = new ListTopicsCommand({});
207
+ const { Topics } = await snsClient.send(listCommand);
646
208
 
647
- async publishWithAttributes(
648
- topicArn: string,
649
- message: string,
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
- ### Event-Driven Notification Service
673
-
214
+ ### Subscriptions
674
215
  ```typescript
675
- interface NotificationEvent {
676
- type: 'user.signup' | 'order.created' | 'payment.failed' | 'system.alert';
677
- userId?: string;
678
- orderId?: string;
679
- data: any;
680
- timestamp: string;
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
- return this.publishEvent(topicArn, event);
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
- ## Best Practices
759
-
760
- ### 1. Error Handling
761
-
233
+ ### Publishing
762
234
  ```typescript
763
- async safeSnsOperation<T>(operation: () => Promise<T>): Promise<T | null> {
764
- try {
765
- return await operation();
766
- } catch (error: any) {
767
- if (error.name === 'NotFound') {
768
- console.warn('SNS resource not found');
769
- return null;
770
- } else if (error.name === 'InvalidParameter') {
771
- console.error('Invalid SNS parameter:', error.message);
772
- throw new Error('Invalid SNS parameter');
773
- } else {
774
- console.error('SNS operation failed:', error);
775
- throw error;
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
- ### 2. Message Size Optimization
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
- ### 3. Phone Number Validation
257
+ ```bash
258
+ # Required
259
+ AWS_REGION=us-east-1
798
260
 
799
- ```typescript
800
- validatePhoneNumber(phoneNumber: string): boolean {
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
- ## Testing
265
+ ## Limitations
808
266
 
809
- ```typescript
810
- import { Test, TestingModule } from '@nestjs/testing';
811
- import { ServerAwsSnsModule } from '@onivoro/server-aws-sns';
812
- import { SNSClient } from '@aws-sdk/client-sns';
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
- describe('SNSClient', () => {
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
- ### Exported Services
841
- - `SNSClient`: AWS SNS client instance (from @aws-sdk/client-sns)
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
- This library is licensed under the MIT License. See the LICENSE file in this package for details.
283
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onivoro/server-aws-sns",
3
- "version": "24.30.12",
3
+ "version": "24.30.14",
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.12",
14
- "@onivoro/server-common": "24.30.12",
13
+ "@onivoro/server-aws-credential-providers": "24.30.14",
14
+ "@onivoro/server-common": "24.30.14",
15
15
  "tslib": "^2.3.0"
16
16
  },
17
17
  "peerDependencies": {