@onivoro/server-aws-sns 24.0.2

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 ADDED
@@ -0,0 +1,845 @@
1
+ # @onivoro/server-aws-sns
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.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @onivoro/server-aws-sns
9
+ ```
10
+
11
+ ## Features
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
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Module Configuration
27
+
28
+ ```typescript
29
+ import { ServerAwsSnsModule } from '@onivoro/server-aws-sns';
30
+
31
+ @Module({
32
+ imports: [
33
+ ServerAwsSnsModule.configure({
34
+ AWS_REGION: 'us-east-1',
35
+ AWS_PROFILE: process.env.AWS_PROFILE || 'default',
36
+ }),
37
+ ],
38
+ })
39
+ export class AppModule {}
40
+ ```
41
+
42
+ ### 2. Basic Usage
43
+
44
+ ```typescript
45
+ import { SNSClient, PublishCommand, CreateTopicCommand } from '@aws-sdk/client-sns';
46
+
47
+ @Injectable()
48
+ export class NotificationService {
49
+ constructor(private snsClient: SNSClient) {}
50
+
51
+ async publishMessage(topicArn: string, message: string, subject?: string) {
52
+ const params = {
53
+ TopicArn: topicArn,
54
+ Message: message,
55
+ Subject: subject
56
+ };
57
+
58
+ return this.snsClient.send(new PublishCommand(params));
59
+ }
60
+
61
+ async sendSMS(phoneNumber: string, message: string) {
62
+ const params = {
63
+ PhoneNumber: phoneNumber,
64
+ Message: message
65
+ };
66
+
67
+ return this.snsClient.send(new PublishCommand(params));
68
+ }
69
+
70
+ async createTopic(topicName: string) {
71
+ const params = {
72
+ 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));
150
+ }
151
+
152
+ async setTopicDisplayName(topicArn: string, displayName: string) {
153
+ const params = {
154
+ 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);
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Subscription Management Service
185
+
186
+ ```typescript
187
+ import {
188
+ SNSClient,
189
+ SubscribeCommand,
190
+ UnsubscribeCommand,
191
+ ListSubscriptionsByTopicCommand,
192
+ ConfirmSubscriptionCommand
193
+ } from '@aws-sdk/client-sns';
194
+
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
+ }
298
+
299
+ @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,
335
+ 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
+ };
380
+
381
+ return this.snsClient.send(new PublishCommand(params));
382
+ }
383
+
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
416
+ );
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
+
470
+ const apnsPayload = {
471
+ aps: {
472
+ alert: message,
473
+ badge: badge,
474
+ sound: sound || 'default'
475
+ }
476
+ };
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
+ }
493
+
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));
519
+ }
520
+ }
521
+ ```
522
+
523
+ ### SMS Service
524
+
525
+ ```typescript
526
+ import { SNSClient, PublishCommand, SetSMSAttributesCommand, GetSMSAttributesCommand } from '@aws-sdk/client-sns';
527
+
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
+ }))
576
+ );
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
+ }
590
+
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
+ });
601
+ }
602
+ }
603
+ ```
604
+
605
+ ## Advanced Usage
606
+
607
+ ### Message Filtering Service
608
+
609
+ ```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
+ };
643
+
644
+ return this.snsClient.send(new SetSubscriptionAttributesCommand(params));
645
+ }
646
+
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
+ }
670
+ ```
671
+
672
+ ### Event-Driven Notification Service
673
+
674
+ ```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
+ };
752
+
753
+ return this.publishEvent(topicArn, event);
754
+ }
755
+ }
756
+ ```
757
+
758
+ ## Best Practices
759
+
760
+ ### 1. Error Handling
761
+
762
+ ```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
+ }
779
+ ```
780
+
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
+ ```
796
+
797
+ ### 3. Phone Number Validation
798
+
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
+ }
805
+ ```
806
+
807
+ ## Testing
808
+
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';
813
+
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
839
+
840
+ ### Exported Services
841
+ - `SNSClient`: AWS SNS client instance (from @aws-sdk/client-sns)
842
+
843
+ ## License
844
+
845
+ This package is part of the Onivoro monorepo and follows the same licensing terms.
package/jest.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ /* eslint-disable */
2
+ export default {
3
+ displayName: 'lib-server-aws-sns',
4
+ preset: '../../../jest.preset.js',
5
+ testEnvironment: 'node',
6
+ transform: {
7
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
8
+ },
9
+ moduleFileExtensions: ['ts', 'js', 'html'],
10
+ coverageDirectory: '../../../coverage/libs/server/aws-sns',
11
+ };
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@onivoro/server-aws-sns",
3
+ "version": "24.0.2",
4
+ "type": "commonjs",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "dependencies": {
8
+ "@onivoro/server-aws-credential-providers": "24.0.2",
9
+ "@onivoro/server-common": "24.0.2",
10
+ "tslib": "^2.3.0"
11
+ },
12
+ "peerDependencies": {
13
+ "@aws-sdk/client-sns": "~3.782.0",
14
+ "@nestjs/common": "~10.4.6"
15
+ }
16
+ }
package/project.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "lib-server-aws-sns",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/server/aws-sns/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "build": {
8
+ "executor": "@nx/js:tsc",
9
+ "outputs": ["{options.outputPath}"],
10
+ "options": {
11
+ "outputPath": "dist/libs/server/aws-sns",
12
+ "main": "libs/server/aws-sns/src/index.ts",
13
+ "tsConfig": "libs/server/aws-sns/tsconfig.lib.json",
14
+ "assets": [
15
+ "libs/server/aws-sns/README.md",
16
+ "libs/server/aws-sns/package.json"
17
+ ],
18
+ "declaration": true
19
+ }
20
+ }
21
+ },
22
+ "tags": []
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './lib/classes/server-aws-sns-config.class';
2
+
3
+ export * from './lib/server-aws-sns.module';
@@ -0,0 +1,4 @@
1
+ export class ServerAwsSnsConfig {
2
+ AWS_PROFILE?: string;
3
+ AWS_REGION: string;
4
+ }
@@ -0,0 +1,32 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { moduleFactory } from '@onivoro/server-common';
3
+ import { SNSClient } from '@aws-sdk/client-sns';
4
+ import { ServerAwsSnsConfig } from './classes/server-aws-sns-config.class';
5
+ import { AwsCredentials, ServerAwsCredentialProvidersModule } from '@onivoro/server-aws-credential-providers';
6
+
7
+ let snsClient: SNSClient | null = null;
8
+
9
+ @Module({
10
+ })
11
+ export class ServerAwsSnsModule {
12
+ static configure(config: ServerAwsSnsConfig) {
13
+ return moduleFactory({
14
+ module: ServerAwsSnsModule,
15
+ imports: [ServerAwsCredentialProvidersModule.configure(config)],
16
+ providers: [
17
+ {
18
+ provide: SNSClient,
19
+ useFactory: (credentials: AwsCredentials) => snsClient
20
+ ? snsClient
21
+ : snsClient = new SNSClient({
22
+ region: config.AWS_REGION,
23
+ logger: console,
24
+ credentials
25
+ }),
26
+ inject: [AwsCredentials]
27
+ },
28
+ { provide: ServerAwsSnsConfig, useValue: config },
29
+ ],
30
+ })
31
+ }
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../../tsconfig.server.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../../dist/out-tsc"
5
+ },
6
+ "files": [],
7
+ "include": [],
8
+ "references": [
9
+ {
10
+ "path": "./tsconfig.lib.json"
11
+ },
12
+ {
13
+ "path": "./tsconfig.spec.json"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": true
5
+ },
6
+ "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
7
+ "include": ["**/*.ts"]
8
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": [
5
+ "jest",
6
+ "node"
7
+ ]
8
+ },
9
+ "include": [
10
+ "jest.config.ts",
11
+ "**/*.test.ts",
12
+ "**/*.spec.ts",
13
+ "**/*.test.tsx",
14
+ "**/*.spec.tsx",
15
+ "**/*.test.js",
16
+ "**/*.spec.js",
17
+ "**/*.test.jsx",
18
+ "**/*.spec.jsx",
19
+ "**/*.d.ts"
20
+ ]
21
+ }