@stacksjs/ts-cloud-core 0.1.3 → 0.1.6
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 +98 -13
- package/package.json +12 -3
- package/src/advanced-features.test.ts +0 -465
- package/src/aws/cloudformation.ts +0 -421
- package/src/aws/cloudfront.ts +0 -158
- package/src/aws/credentials.test.ts +0 -132
- package/src/aws/credentials.ts +0 -545
- package/src/aws/index.ts +0 -87
- package/src/aws/s3.test.ts +0 -188
- package/src/aws/s3.ts +0 -1088
- package/src/aws/signature.test.ts +0 -670
- package/src/aws/signature.ts +0 -1155
- package/src/backup/disaster-recovery.test.ts +0 -726
- package/src/backup/disaster-recovery.ts +0 -500
- package/src/backup/index.ts +0 -34
- package/src/backup/manager.test.ts +0 -498
- package/src/backup/manager.ts +0 -432
- package/src/cicd/circleci.ts +0 -430
- package/src/cicd/github-actions.ts +0 -424
- package/src/cicd/gitlab-ci.ts +0 -255
- package/src/cicd/index.ts +0 -8
- package/src/cli/history.ts +0 -396
- package/src/cli/index.ts +0 -10
- package/src/cli/progress.ts +0 -458
- package/src/cli/repl.ts +0 -454
- package/src/cli/suggestions.ts +0 -327
- package/src/cli/table.test.ts +0 -319
- package/src/cli/table.ts +0 -332
- package/src/cloudformation/builder.test.ts +0 -327
- package/src/cloudformation/builder.ts +0 -378
- package/src/cloudformation/builders/api-gateway.ts +0 -449
- package/src/cloudformation/builders/cache.ts +0 -334
- package/src/cloudformation/builders/cdn.ts +0 -278
- package/src/cloudformation/builders/compute.ts +0 -485
- package/src/cloudformation/builders/database.ts +0 -392
- package/src/cloudformation/builders/functions.ts +0 -343
- package/src/cloudformation/builders/messaging.ts +0 -140
- package/src/cloudformation/builders/monitoring.ts +0 -300
- package/src/cloudformation/builders/network.ts +0 -264
- package/src/cloudformation/builders/queue.ts +0 -147
- package/src/cloudformation/builders/security.ts +0 -399
- package/src/cloudformation/builders/storage.ts +0 -285
- package/src/cloudformation/index.ts +0 -30
- package/src/cloudformation/types.ts +0 -173
- package/src/compliance/aws-config.ts +0 -543
- package/src/compliance/cloudtrail.ts +0 -376
- package/src/compliance/compliance.test.ts +0 -423
- package/src/compliance/guardduty.ts +0 -446
- package/src/compliance/index.ts +0 -66
- package/src/compliance/security-hub.ts +0 -456
- package/src/containers/build-optimization.ts +0 -416
- package/src/containers/containers.test.ts +0 -508
- package/src/containers/image-scanning.ts +0 -360
- package/src/containers/index.ts +0 -9
- package/src/containers/registry.ts +0 -293
- package/src/containers/service-mesh.ts +0 -520
- package/src/database/database.test.ts +0 -762
- package/src/database/index.ts +0 -9
- package/src/database/migrations.ts +0 -444
- package/src/database/performance.ts +0 -528
- package/src/database/replicas.ts +0 -534
- package/src/database/users.ts +0 -494
- package/src/dependency-graph.ts +0 -143
- package/src/deployment/ab-testing.ts +0 -582
- package/src/deployment/blue-green.ts +0 -452
- package/src/deployment/canary.ts +0 -500
- package/src/deployment/deployment.test.ts +0 -526
- package/src/deployment/index.ts +0 -61
- package/src/deployment/progressive.ts +0 -62
- package/src/dns/dns.test.ts +0 -641
- package/src/dns/dnssec.ts +0 -315
- package/src/dns/index.ts +0 -8
- package/src/dns/resolver.ts +0 -496
- package/src/dns/routing.ts +0 -593
- package/src/email/advanced/analytics.ts +0 -445
- package/src/email/advanced/index.ts +0 -11
- package/src/email/advanced/rules.ts +0 -465
- package/src/email/advanced/scheduling.ts +0 -352
- package/src/email/advanced/search.ts +0 -412
- package/src/email/advanced/shared-mailboxes.ts +0 -404
- package/src/email/advanced/templates.ts +0 -455
- package/src/email/advanced/threading.ts +0 -281
- package/src/email/analytics.ts +0 -467
- package/src/email/bounce-handling.ts +0 -425
- package/src/email/email.test.ts +0 -431
- package/src/email/handlers/__tests__/inbound.test.ts +0 -38
- package/src/email/handlers/__tests__/outbound.test.ts +0 -37
- package/src/email/handlers/converter.ts +0 -227
- package/src/email/handlers/feedback.ts +0 -228
- package/src/email/handlers/inbound.ts +0 -169
- package/src/email/handlers/outbound.ts +0 -178
- package/src/email/index.ts +0 -15
- package/src/email/reputation.ts +0 -303
- package/src/email/templates.ts +0 -352
- package/src/errors/index.test.ts +0 -434
- package/src/errors/index.ts +0 -416
- package/src/health-checks/index.ts +0 -40
- package/src/index.ts +0 -360
- package/src/intrinsic-functions.ts +0 -118
- package/src/lambda/concurrency.ts +0 -330
- package/src/lambda/destinations.ts +0 -345
- package/src/lambda/dlq.ts +0 -425
- package/src/lambda/index.ts +0 -11
- package/src/lambda/lambda.test.ts +0 -840
- package/src/lambda/layers.ts +0 -263
- package/src/lambda/versions.ts +0 -376
- package/src/lambda/vpc.ts +0 -399
- package/src/local/config.ts +0 -114
- package/src/local/index.ts +0 -6
- package/src/local/mock-aws.ts +0 -351
- package/src/modules/ai.ts +0 -340
- package/src/modules/api.ts +0 -478
- package/src/modules/auth.ts +0 -805
- package/src/modules/cache.ts +0 -417
- package/src/modules/cdn.ts +0 -1062
- package/src/modules/communication.ts +0 -1094
- package/src/modules/compute.ts +0 -3348
- package/src/modules/database.ts +0 -554
- package/src/modules/deployment.ts +0 -1079
- package/src/modules/dns.ts +0 -337
- package/src/modules/email.ts +0 -1538
- package/src/modules/filesystem.ts +0 -515
- package/src/modules/index.ts +0 -32
- package/src/modules/messaging.ts +0 -486
- package/src/modules/monitoring.ts +0 -2086
- package/src/modules/network.ts +0 -664
- package/src/modules/parameter-store.ts +0 -325
- package/src/modules/permissions.ts +0 -1081
- package/src/modules/phone.ts +0 -494
- package/src/modules/queue.ts +0 -1260
- package/src/modules/redirects.ts +0 -464
- package/src/modules/registry.ts +0 -699
- package/src/modules/search.ts +0 -401
- package/src/modules/secrets.ts +0 -416
- package/src/modules/security.ts +0 -731
- package/src/modules/sms.ts +0 -389
- package/src/modules/storage.ts +0 -1120
- package/src/modules/workflow.ts +0 -680
- package/src/multi-account/config.ts +0 -521
- package/src/multi-account/index.ts +0 -7
- package/src/multi-account/manager.ts +0 -427
- package/src/multi-region/cross-region.ts +0 -410
- package/src/multi-region/index.ts +0 -8
- package/src/multi-region/manager.ts +0 -483
- package/src/multi-region/regions.ts +0 -435
- package/src/network-security/index.ts +0 -48
- package/src/observability/index.ts +0 -9
- package/src/observability/logs.ts +0 -522
- package/src/observability/metrics.ts +0 -460
- package/src/observability/observability.test.ts +0 -782
- package/src/observability/synthetics.ts +0 -568
- package/src/observability/xray.ts +0 -358
- package/src/phone/advanced/analytics.ts +0 -349
- package/src/phone/advanced/callbacks.ts +0 -428
- package/src/phone/advanced/index.ts +0 -8
- package/src/phone/advanced/ivr-builder.ts +0 -504
- package/src/phone/advanced/recording.ts +0 -310
- package/src/phone/handlers/__tests__/incoming-call.test.ts +0 -40
- package/src/phone/handlers/incoming-call.ts +0 -117
- package/src/phone/handlers/missed-call.ts +0 -116
- package/src/phone/handlers/voicemail.ts +0 -179
- package/src/phone/index.ts +0 -9
- package/src/presets/api-backend.ts +0 -134
- package/src/presets/data-pipeline.ts +0 -204
- package/src/presets/extend.test.ts +0 -295
- package/src/presets/extend.ts +0 -297
- package/src/presets/fullstack-app.ts +0 -144
- package/src/presets/index.ts +0 -27
- package/src/presets/jamstack.ts +0 -135
- package/src/presets/microservices.ts +0 -167
- package/src/presets/ml-api.ts +0 -208
- package/src/presets/nodejs-server.ts +0 -104
- package/src/presets/nodejs-serverless.ts +0 -114
- package/src/presets/realtime-app.ts +0 -184
- package/src/presets/static-site.ts +0 -64
- package/src/presets/traditional-web-app.ts +0 -339
- package/src/presets/wordpress.ts +0 -138
- package/src/preview/github.test.ts +0 -249
- package/src/preview/github.ts +0 -297
- package/src/preview/index.ts +0 -37
- package/src/preview/manager.test.ts +0 -440
- package/src/preview/manager.ts +0 -326
- package/src/preview/notifications.test.ts +0 -582
- package/src/preview/notifications.ts +0 -341
- package/src/queue/batch-processing.ts +0 -402
- package/src/queue/dlq-monitoring.ts +0 -402
- package/src/queue/fifo.ts +0 -342
- package/src/queue/index.ts +0 -9
- package/src/queue/management.ts +0 -428
- package/src/queue/queue.test.ts +0 -429
- package/src/resource-mgmt/index.ts +0 -39
- package/src/resource-naming.ts +0 -62
- package/src/s3/index.ts +0 -523
- package/src/schema/cloud-config.schema.json +0 -554
- package/src/schema/index.ts +0 -68
- package/src/security/certificate-manager.ts +0 -492
- package/src/security/index.ts +0 -9
- package/src/security/scanning.ts +0 -545
- package/src/security/secrets-manager.ts +0 -476
- package/src/security/secrets-rotation.ts +0 -456
- package/src/security/security.test.ts +0 -738
- package/src/sms/advanced/ab-testing.ts +0 -389
- package/src/sms/advanced/analytics.ts +0 -336
- package/src/sms/advanced/campaigns.ts +0 -523
- package/src/sms/advanced/chatbot.ts +0 -224
- package/src/sms/advanced/index.ts +0 -10
- package/src/sms/advanced/link-tracking.ts +0 -248
- package/src/sms/advanced/mms.ts +0 -308
- package/src/sms/handlers/__tests__/send.test.ts +0 -40
- package/src/sms/handlers/delivery-status.ts +0 -133
- package/src/sms/handlers/receive.ts +0 -162
- package/src/sms/handlers/send.ts +0 -174
- package/src/sms/index.ts +0 -9
- package/src/stack-diff.ts +0 -389
- package/src/static-site/index.ts +0 -85
- package/src/template-builder.ts +0 -110
- package/src/template-validator.ts +0 -574
- package/src/utils/cache.ts +0 -291
- package/src/utils/diff.ts +0 -269
- package/src/utils/hash.ts +0 -227
- package/src/utils/index.ts +0 -8
- package/src/utils/parallel.ts +0 -294
- package/src/validators/credentials.test.ts +0 -274
- package/src/validators/credentials.ts +0 -233
- package/src/validators/quotas.test.ts +0 -434
- package/src/validators/quotas.ts +0 -217
- package/test/ai.test.ts +0 -327
- package/test/api.test.ts +0 -511
- package/test/auth.test.ts +0 -632
- package/test/cache.test.ts +0 -406
- package/test/cdn.test.ts +0 -247
- package/test/compute.test.ts +0 -861
- package/test/database.test.ts +0 -523
- package/test/deployment.test.ts +0 -499
- package/test/dns.test.ts +0 -270
- package/test/email.test.ts +0 -439
- package/test/filesystem.test.ts +0 -382
- package/test/integration.test.ts +0 -350
- package/test/messaging.test.ts +0 -514
- package/test/monitoring.test.ts +0 -634
- package/test/network.test.ts +0 -425
- package/test/permissions.test.ts +0 -488
- package/test/queue.test.ts +0 -484
- package/test/registry.test.ts +0 -306
- package/test/security.test.ts +0 -462
- package/test/storage.test.ts +0 -463
- package/test/template-validator.test.ts +0 -559
- package/test/workflow.test.ts +0 -592
- package/tsconfig.json +0 -16
- package/tsconfig.tsbuildinfo +0 -1
package/src/sms/advanced/mms.ts
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MMS Support (Images, Media)
|
|
3
|
-
*
|
|
4
|
-
* Provides multimedia messaging capabilities
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface MmsMessage {
|
|
8
|
-
to: string
|
|
9
|
-
body?: string
|
|
10
|
-
mediaUrls: string[]
|
|
11
|
-
mediaType?: 'image' | 'video' | 'audio' | 'document'
|
|
12
|
-
fallbackSms?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface MmsMedia {
|
|
16
|
-
url: string
|
|
17
|
-
contentType: string
|
|
18
|
-
size: number
|
|
19
|
-
filename?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* MMS Module
|
|
24
|
-
*/
|
|
25
|
-
export class MmsSupport {
|
|
26
|
-
/**
|
|
27
|
-
* Lambda code for MMS sending
|
|
28
|
-
*/
|
|
29
|
-
static MmsSenderCode = `
|
|
30
|
-
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
|
|
31
|
-
const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
|
|
32
|
-
const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
|
|
33
|
-
|
|
34
|
-
const s3 = new S3Client({});
|
|
35
|
-
const sns = new SNSClient({});
|
|
36
|
-
const dynamodb = new DynamoDBClient({});
|
|
37
|
-
|
|
38
|
-
const MEDIA_BUCKET = process.env.MEDIA_BUCKET;
|
|
39
|
-
const MESSAGE_LOG_TABLE = process.env.MESSAGE_LOG_TABLE;
|
|
40
|
-
const ORIGINATION_NUMBER = process.env.ORIGINATION_NUMBER;
|
|
41
|
-
|
|
42
|
-
// Supported media types
|
|
43
|
-
const SUPPORTED_TYPES = {
|
|
44
|
-
'image/jpeg': { maxSize: 1024 * 1024, extension: 'jpg' },
|
|
45
|
-
'image/png': { maxSize: 1024 * 1024, extension: 'png' },
|
|
46
|
-
'image/gif': { maxSize: 1024 * 1024, extension: 'gif' },
|
|
47
|
-
'video/mp4': { maxSize: 5 * 1024 * 1024, extension: 'mp4' },
|
|
48
|
-
'video/3gpp': { maxSize: 5 * 1024 * 1024, extension: '3gp' },
|
|
49
|
-
'audio/mpeg': { maxSize: 1024 * 1024, extension: 'mp3' },
|
|
50
|
-
'audio/wav': { maxSize: 1024 * 1024, extension: 'wav' },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
exports.handler = async (event) => {
|
|
54
|
-
console.log('MMS sender event:', JSON.stringify(event, null, 2));
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const body = JSON.parse(event.body || '{}');
|
|
58
|
-
const { to, text, mediaUrls, fallbackSms } = body;
|
|
59
|
-
|
|
60
|
-
if (!to) {
|
|
61
|
-
return {
|
|
62
|
-
statusCode: 400,
|
|
63
|
-
body: JSON.stringify({ error: 'Missing recipient phone number' }),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!mediaUrls || mediaUrls.length === 0) {
|
|
68
|
-
return {
|
|
69
|
-
statusCode: 400,
|
|
70
|
-
body: JSON.stringify({ error: 'At least one media URL is required' }),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const messageId = \`mms-\${Date.now()}-\${Math.random().toString(36).substr(2, 9)}\`;
|
|
75
|
-
const now = new Date().toISOString();
|
|
76
|
-
|
|
77
|
-
// Validate and process media
|
|
78
|
-
const processedMedia = [];
|
|
79
|
-
for (const url of mediaUrls) {
|
|
80
|
-
try {
|
|
81
|
-
const media = await processMedia(url, messageId);
|
|
82
|
-
processedMedia.push(media);
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error(\`Failed to process media \${url}:\`, error);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (processedMedia.length === 0) {
|
|
89
|
-
// Fall back to SMS if no media could be processed
|
|
90
|
-
if (fallbackSms) {
|
|
91
|
-
const smsResult = await sendFallbackSms(to, fallbackSms);
|
|
92
|
-
return {
|
|
93
|
-
statusCode: 200,
|
|
94
|
-
body: JSON.stringify({
|
|
95
|
-
messageId,
|
|
96
|
-
type: 'sms_fallback',
|
|
97
|
-
...smsResult,
|
|
98
|
-
}),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
statusCode: 400,
|
|
103
|
-
body: JSON.stringify({ error: 'No valid media could be processed' }),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Send MMS via SNS (carrier-dependent)
|
|
108
|
-
// Note: True MMS requires carrier integration or third-party service
|
|
109
|
-
// This implementation uses SNS with media URLs as a simplified approach
|
|
110
|
-
const message = {
|
|
111
|
-
to,
|
|
112
|
-
text: text || '',
|
|
113
|
-
mediaUrls: processedMedia.map(m => m.publicUrl),
|
|
114
|
-
messageId,
|
|
115
|
-
timestamp: now,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const snsResult = await sns.send(new PublishCommand({
|
|
119
|
-
PhoneNumber: to,
|
|
120
|
-
Message: text || 'You have received a multimedia message. View it here: ' + processedMedia[0].publicUrl,
|
|
121
|
-
MessageAttributes: {
|
|
122
|
-
'AWS.SNS.SMS.SMSType': {
|
|
123
|
-
DataType: 'String',
|
|
124
|
-
StringValue: 'Transactional',
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
}));
|
|
128
|
-
|
|
129
|
-
// Log message
|
|
130
|
-
await dynamodb.send(new PutItemCommand({
|
|
131
|
-
TableName: MESSAGE_LOG_TABLE,
|
|
132
|
-
Item: {
|
|
133
|
-
messageId: { S: messageId },
|
|
134
|
-
type: { S: 'mms' },
|
|
135
|
-
to: { S: to },
|
|
136
|
-
text: { S: text || '' },
|
|
137
|
-
mediaUrls: { SS: processedMedia.map(m => m.publicUrl) },
|
|
138
|
-
snsMessageId: { S: snsResult.MessageId || '' },
|
|
139
|
-
sentAt: { S: now },
|
|
140
|
-
ttl: { N: String(Math.floor(Date.now() / 1000) + 90 * 24 * 60 * 60) },
|
|
141
|
-
},
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
statusCode: 200,
|
|
146
|
-
headers: { 'Content-Type': 'application/json' },
|
|
147
|
-
body: JSON.stringify({
|
|
148
|
-
messageId,
|
|
149
|
-
type: 'mms',
|
|
150
|
-
mediaCount: processedMedia.length,
|
|
151
|
-
snsMessageId: snsResult.MessageId,
|
|
152
|
-
}),
|
|
153
|
-
};
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error('Error sending MMS:', error);
|
|
156
|
-
return {
|
|
157
|
-
statusCode: 500,
|
|
158
|
-
body: JSON.stringify({ error: error.message }),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
async function processMedia(url, messageId) {
|
|
164
|
-
// Fetch media
|
|
165
|
-
const response = await fetch(url);
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
throw new Error(\`Failed to fetch media: \${response.status}\`);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const contentType = response.headers.get('content-type') || 'application/octet-stream';
|
|
171
|
-
const typeConfig = SUPPORTED_TYPES[contentType];
|
|
172
|
-
|
|
173
|
-
if (!typeConfig) {
|
|
174
|
-
throw new Error(\`Unsupported media type: \${contentType}\`);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const buffer = await response.arrayBuffer();
|
|
178
|
-
if (buffer.byteLength > typeConfig.maxSize) {
|
|
179
|
-
throw new Error(\`Media too large: \${buffer.byteLength} bytes (max: \${typeConfig.maxSize})\`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Upload to S3
|
|
183
|
-
const key = \`mms/\${messageId}/media.\${typeConfig.extension}\`;
|
|
184
|
-
await s3.send(new PutObjectCommand({
|
|
185
|
-
Bucket: MEDIA_BUCKET,
|
|
186
|
-
Key: key,
|
|
187
|
-
Body: Buffer.from(buffer),
|
|
188
|
-
ContentType: contentType,
|
|
189
|
-
}));
|
|
190
|
-
|
|
191
|
-
// Generate public URL (requires bucket to be configured for public access or use presigned URL)
|
|
192
|
-
const publicUrl = \`https://\${MEDIA_BUCKET}.s3.amazonaws.com/\${key}\`;
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
originalUrl: url,
|
|
196
|
-
publicUrl,
|
|
197
|
-
contentType,
|
|
198
|
-
size: buffer.byteLength,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function sendFallbackSms(to, message) {
|
|
203
|
-
const result = await sns.send(new PublishCommand({
|
|
204
|
-
PhoneNumber: to,
|
|
205
|
-
Message: message,
|
|
206
|
-
MessageAttributes: {
|
|
207
|
-
'AWS.SNS.SMS.SMSType': {
|
|
208
|
-
DataType: 'String',
|
|
209
|
-
StringValue: 'Transactional',
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
}));
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
snsMessageId: result.MessageId,
|
|
216
|
-
fallback: true,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
`
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Create media storage bucket
|
|
223
|
-
*/
|
|
224
|
-
static createMediaBucket(config: { slug: string }): Record<string, any> {
|
|
225
|
-
return {
|
|
226
|
-
[`${config.slug}MmsMediaBucket`]: {
|
|
227
|
-
Type: 'AWS::S3::Bucket',
|
|
228
|
-
Properties: {
|
|
229
|
-
BucketName: `${config.slug}-mms-media`,
|
|
230
|
-
LifecycleConfiguration: {
|
|
231
|
-
Rules: [
|
|
232
|
-
{
|
|
233
|
-
Id: 'DeleteOldMedia',
|
|
234
|
-
Status: 'Enabled',
|
|
235
|
-
ExpirationInDays: 30,
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
},
|
|
239
|
-
CorsConfiguration: {
|
|
240
|
-
CorsRules: [
|
|
241
|
-
{
|
|
242
|
-
AllowedOrigins: ['*'],
|
|
243
|
-
AllowedMethods: ['GET'],
|
|
244
|
-
AllowedHeaders: ['*'],
|
|
245
|
-
MaxAge: 3600,
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Create MMS sender Lambda
|
|
256
|
-
*/
|
|
257
|
-
static createMmsSenderLambda(config: {
|
|
258
|
-
slug: string
|
|
259
|
-
roleArn: string
|
|
260
|
-
mediaBucket: string
|
|
261
|
-
messageLogTable: string
|
|
262
|
-
originationNumber?: string
|
|
263
|
-
}): Record<string, any> {
|
|
264
|
-
return {
|
|
265
|
-
[`${config.slug}MmsSenderLambda`]: {
|
|
266
|
-
Type: 'AWS::Lambda::Function',
|
|
267
|
-
Properties: {
|
|
268
|
-
FunctionName: `${config.slug}-mms-sender`,
|
|
269
|
-
Runtime: 'nodejs20.x',
|
|
270
|
-
Handler: 'index.handler',
|
|
271
|
-
Role: config.roleArn,
|
|
272
|
-
Timeout: 60,
|
|
273
|
-
MemorySize: 512,
|
|
274
|
-
Code: {
|
|
275
|
-
ZipFile: MmsSupport.MmsSenderCode,
|
|
276
|
-
},
|
|
277
|
-
Environment: {
|
|
278
|
-
Variables: {
|
|
279
|
-
MEDIA_BUCKET: config.mediaBucket,
|
|
280
|
-
MESSAGE_LOG_TABLE: config.messageLogTable,
|
|
281
|
-
ORIGINATION_NUMBER: config.originationNumber || '',
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Supported media types
|
|
291
|
-
*/
|
|
292
|
-
static readonly SupportedMediaTypes = {
|
|
293
|
-
image: ['image/jpeg', 'image/png', 'image/gif'],
|
|
294
|
-
video: ['video/mp4', 'video/3gpp'],
|
|
295
|
-
audio: ['audio/mpeg', 'audio/wav'],
|
|
296
|
-
} as const
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Media size limits (in bytes)
|
|
300
|
-
*/
|
|
301
|
-
static readonly MediaSizeLimits: { image: number, video: number, audio: number } = {
|
|
302
|
-
image: 1024 * 1024, // 1MB
|
|
303
|
-
video: 5 * 1024 * 1024, // 5MB
|
|
304
|
-
audio: 1024 * 1024, // 1MB
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export default MmsSupport
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test'
|
|
2
|
-
import { handler } from '../send'
|
|
3
|
-
|
|
4
|
-
describe('SMS Send Handler', () => {
|
|
5
|
-
it('should export handler code as string', () => {
|
|
6
|
-
expect(typeof handler).toBe('string')
|
|
7
|
-
expect(handler).toContain('exports.handler')
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('should contain Pinpoint client import', () => {
|
|
11
|
-
expect(handler).toContain('PinpointClient')
|
|
12
|
-
expect(handler).toContain('SendMessagesCommand')
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('should contain SNS client as fallback', () => {
|
|
16
|
-
expect(handler).toContain('SNSClient')
|
|
17
|
-
expect(handler).toContain('PublishCommand')
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should support templated messages', () => {
|
|
21
|
-
expect(handler).toContain('template')
|
|
22
|
-
expect(handler).toContain('templateData')
|
|
23
|
-
expect(handler).toContain('resolveTemplate')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('should log messages to DynamoDB', () => {
|
|
27
|
-
expect(handler).toContain('MESSAGE_LOG_TABLE')
|
|
28
|
-
expect(handler).toContain('messageId')
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should support message types', () => {
|
|
32
|
-
expect(handler).toContain('TRANSACTIONAL')
|
|
33
|
-
expect(handler).toContain('PROMOTIONAL')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('should handle delivery status', () => {
|
|
37
|
-
expect(handler).toContain('DeliveryStatus')
|
|
38
|
-
expect(handler).toContain('SENT')
|
|
39
|
-
})
|
|
40
|
-
})
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMS Delivery Status Lambda Handler
|
|
3
|
-
*
|
|
4
|
-
* Processes delivery status updates:
|
|
5
|
-
* - Process delivery receipts
|
|
6
|
-
* - Update message status
|
|
7
|
-
* - Handle failures
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export const handler = `
|
|
11
|
-
const { DynamoDBClient, UpdateItemCommand } = require('@aws-sdk/client-dynamodb');
|
|
12
|
-
const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
|
|
13
|
-
|
|
14
|
-
const dynamodb = new DynamoDBClient({});
|
|
15
|
-
const sns = new SNSClient({});
|
|
16
|
-
|
|
17
|
-
exports.handler = async (event) => {
|
|
18
|
-
console.log('SMS delivery status event:', JSON.stringify(event, null, 2));
|
|
19
|
-
|
|
20
|
-
const messageLogTable = process.env.MESSAGE_LOG_TABLE;
|
|
21
|
-
const notificationTopicArn = process.env.NOTIFICATION_TOPIC_ARN;
|
|
22
|
-
const webhookUrl = process.env.WEBHOOK_URL;
|
|
23
|
-
|
|
24
|
-
for (const record of event.Records) {
|
|
25
|
-
try {
|
|
26
|
-
// Parse delivery status from SNS/Pinpoint
|
|
27
|
-
const message = JSON.parse(record.Sns?.Message || record.body || '{}');
|
|
28
|
-
|
|
29
|
-
const {
|
|
30
|
-
eventType,
|
|
31
|
-
messageId,
|
|
32
|
-
destinationPhoneNumber,
|
|
33
|
-
messageStatus,
|
|
34
|
-
messageStatusDescription,
|
|
35
|
-
isoCountryCode,
|
|
36
|
-
mcc,
|
|
37
|
-
mnc,
|
|
38
|
-
priceInMillicentsUSD,
|
|
39
|
-
} = message;
|
|
40
|
-
|
|
41
|
-
if (!messageId) {
|
|
42
|
-
console.log('No messageId in delivery status');
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const timestamp = new Date().toISOString();
|
|
47
|
-
const status = messageStatus || eventType || 'UNKNOWN';
|
|
48
|
-
|
|
49
|
-
console.log(\`Delivery status for \${messageId}: \${status}\`);
|
|
50
|
-
|
|
51
|
-
// Update message log
|
|
52
|
-
if (messageLogTable) {
|
|
53
|
-
await dynamodb.send(new UpdateItemCommand({
|
|
54
|
-
TableName: messageLogTable,
|
|
55
|
-
Key: {
|
|
56
|
-
messageId: { S: messageId },
|
|
57
|
-
},
|
|
58
|
-
UpdateExpression: 'SET deliveryStatus = :status, statusDescription = :desc, deliveredAt = :at, priceMillicents = :price, countryCode = :country',
|
|
59
|
-
ExpressionAttributeValues: {
|
|
60
|
-
':status': { S: status },
|
|
61
|
-
':desc': { S: messageStatusDescription || '' },
|
|
62
|
-
':at': { S: timestamp },
|
|
63
|
-
':price': { N: String(priceInMillicentsUSD || 0) },
|
|
64
|
-
':country': { S: isoCountryCode || '' },
|
|
65
|
-
},
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Handle failures - notify admin
|
|
70
|
-
const isFailure = ['FAILED', 'UNREACHABLE', 'UNKNOWN', 'CARRIER_UNREACHABLE', 'BLOCKED', 'CARRIER_BLOCKED', 'INVALID', 'INVALID_MESSAGE', 'OPTED_OUT'].includes(status);
|
|
71
|
-
|
|
72
|
-
if (isFailure) {
|
|
73
|
-
console.log(\`SMS delivery failed: \${status} - \${messageStatusDescription}\`);
|
|
74
|
-
|
|
75
|
-
// Send failure notification
|
|
76
|
-
if (notificationTopicArn) {
|
|
77
|
-
await sns.send(new PublishCommand({
|
|
78
|
-
TopicArn: notificationTopicArn,
|
|
79
|
-
Subject: \`SMS Delivery Failed: \${status}\`,
|
|
80
|
-
Message: JSON.stringify({
|
|
81
|
-
type: 'sms_delivery_failed',
|
|
82
|
-
messageId,
|
|
83
|
-
to: destinationPhoneNumber,
|
|
84
|
-
status,
|
|
85
|
-
description: messageStatusDescription,
|
|
86
|
-
countryCode: isoCountryCode,
|
|
87
|
-
timestamp,
|
|
88
|
-
}, null, 2),
|
|
89
|
-
MessageAttributes: {
|
|
90
|
-
eventType: {
|
|
91
|
-
DataType: 'String',
|
|
92
|
-
StringValue: 'sms_delivery_failed',
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Forward to webhook for all status updates
|
|
100
|
-
if (webhookUrl) {
|
|
101
|
-
try {
|
|
102
|
-
await fetch(webhookUrl, {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: { 'Content-Type': 'application/json' },
|
|
105
|
-
body: JSON.stringify({
|
|
106
|
-
event: 'sms_delivery_status',
|
|
107
|
-
data: {
|
|
108
|
-
messageId,
|
|
109
|
-
to: destinationPhoneNumber,
|
|
110
|
-
status,
|
|
111
|
-
description: messageStatusDescription,
|
|
112
|
-
countryCode: isoCountryCode,
|
|
113
|
-
carrier: { mcc, mnc },
|
|
114
|
-
priceMillicents: priceInMillicentsUSD,
|
|
115
|
-
timestamp,
|
|
116
|
-
},
|
|
117
|
-
}),
|
|
118
|
-
});
|
|
119
|
-
} catch (err) {
|
|
120
|
-
console.error('Webhook failed:', err.message);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error('Error processing delivery status:', error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { statusCode: 200, body: 'OK' };
|
|
130
|
-
};
|
|
131
|
-
`
|
|
132
|
-
|
|
133
|
-
export default handler
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMS Receive Lambda Handler
|
|
3
|
-
*
|
|
4
|
-
* Processes inbound SMS messages:
|
|
5
|
-
* - Process inbound SMS (two-way)
|
|
6
|
-
* - Handle opt-out keywords
|
|
7
|
-
* - Forward to webhook
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export const handler = `
|
|
11
|
-
const { DynamoDBClient, PutItemCommand, GetItemCommand, DeleteItemCommand } = require('@aws-sdk/client-dynamodb');
|
|
12
|
-
const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
|
|
13
|
-
|
|
14
|
-
const dynamodb = new DynamoDBClient({});
|
|
15
|
-
const sns = new SNSClient({});
|
|
16
|
-
|
|
17
|
-
const OPT_OUT_KEYWORDS = ['STOP', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT', 'OPTOUT', 'OPT OUT'];
|
|
18
|
-
const OPT_IN_KEYWORDS = ['START', 'SUBSCRIBE', 'OPTIN', 'OPT IN', 'YES'];
|
|
19
|
-
|
|
20
|
-
exports.handler = async (event) => {
|
|
21
|
-
console.log('SMS receive event:', JSON.stringify(event, null, 2));
|
|
22
|
-
|
|
23
|
-
const optOutTable = process.env.OPT_OUT_TABLE;
|
|
24
|
-
const messageLogTable = process.env.MESSAGE_LOG_TABLE;
|
|
25
|
-
const notificationTopicArn = process.env.NOTIFICATION_TOPIC_ARN;
|
|
26
|
-
const webhookUrl = process.env.WEBHOOK_URL;
|
|
27
|
-
|
|
28
|
-
for (const record of event.Records) {
|
|
29
|
-
try {
|
|
30
|
-
// Parse SNS message from Pinpoint
|
|
31
|
-
const message = JSON.parse(record.Sns?.Message || record.body || '{}');
|
|
32
|
-
|
|
33
|
-
const {
|
|
34
|
-
originationNumber,
|
|
35
|
-
destinationNumber,
|
|
36
|
-
messageBody,
|
|
37
|
-
messageKeyword,
|
|
38
|
-
inboundMessageId,
|
|
39
|
-
} = message;
|
|
40
|
-
|
|
41
|
-
if (!originationNumber || !messageBody) {
|
|
42
|
-
console.log('Missing required fields');
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const timestamp = new Date().toISOString();
|
|
47
|
-
const normalizedBody = messageBody.trim().toUpperCase();
|
|
48
|
-
|
|
49
|
-
// Check for opt-out keywords
|
|
50
|
-
if (OPT_OUT_KEYWORDS.some(kw => normalizedBody === kw || normalizedBody.startsWith(kw + ' '))) {
|
|
51
|
-
console.log(\`Opt-out request from \${originationNumber}\`);
|
|
52
|
-
|
|
53
|
-
if (optOutTable) {
|
|
54
|
-
await dynamodb.send(new PutItemCommand({
|
|
55
|
-
TableName: optOutTable,
|
|
56
|
-
Item: {
|
|
57
|
-
phoneNumber: { S: originationNumber },
|
|
58
|
-
optedOutAt: { S: timestamp },
|
|
59
|
-
keyword: { S: normalizedBody.split(' ')[0] },
|
|
60
|
-
originalMessage: { S: messageBody },
|
|
61
|
-
},
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Send confirmation (optional - check carrier requirements)
|
|
66
|
-
// await sendOptOutConfirmation(originationNumber, destinationNumber);
|
|
67
|
-
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Check for opt-in keywords
|
|
72
|
-
if (OPT_IN_KEYWORDS.some(kw => normalizedBody === kw || normalizedBody.startsWith(kw + ' '))) {
|
|
73
|
-
console.log(\`Opt-in request from \${originationNumber}\`);
|
|
74
|
-
|
|
75
|
-
if (optOutTable) {
|
|
76
|
-
await dynamodb.send(new DeleteItemCommand({
|
|
77
|
-
TableName: optOutTable,
|
|
78
|
-
Key: {
|
|
79
|
-
phoneNumber: { S: originationNumber },
|
|
80
|
-
},
|
|
81
|
-
}));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Log inbound message
|
|
88
|
-
if (messageLogTable) {
|
|
89
|
-
await dynamodb.send(new PutItemCommand({
|
|
90
|
-
TableName: messageLogTable,
|
|
91
|
-
Item: {
|
|
92
|
-
messageId: { S: inboundMessageId || \`inbound-\${Date.now()}\` },
|
|
93
|
-
direction: { S: 'inbound' },
|
|
94
|
-
from: { S: originationNumber },
|
|
95
|
-
to: { S: destinationNumber },
|
|
96
|
-
body: { S: messageBody },
|
|
97
|
-
keyword: { S: messageKeyword || '' },
|
|
98
|
-
receivedAt: { S: timestamp },
|
|
99
|
-
ttl: { N: String(Math.floor(Date.now() / 1000) + 90 * 24 * 60 * 60) },
|
|
100
|
-
},
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Send SNS notification
|
|
105
|
-
if (notificationTopicArn) {
|
|
106
|
-
await sns.send(new PublishCommand({
|
|
107
|
-
TopicArn: notificationTopicArn,
|
|
108
|
-
Subject: 'Inbound SMS',
|
|
109
|
-
Message: JSON.stringify({
|
|
110
|
-
type: 'inbound_sms',
|
|
111
|
-
from: originationNumber,
|
|
112
|
-
to: destinationNumber,
|
|
113
|
-
body: messageBody,
|
|
114
|
-
keyword: messageKeyword,
|
|
115
|
-
timestamp,
|
|
116
|
-
}, null, 2),
|
|
117
|
-
MessageAttributes: {
|
|
118
|
-
eventType: {
|
|
119
|
-
DataType: 'String',
|
|
120
|
-
StringValue: 'inbound_sms',
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
}));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Forward to webhook
|
|
127
|
-
if (webhookUrl) {
|
|
128
|
-
try {
|
|
129
|
-
const response = await fetch(webhookUrl, {
|
|
130
|
-
method: 'POST',
|
|
131
|
-
headers: { 'Content-Type': 'application/json' },
|
|
132
|
-
body: JSON.stringify({
|
|
133
|
-
event: 'inbound_sms',
|
|
134
|
-
data: {
|
|
135
|
-
from: originationNumber,
|
|
136
|
-
to: destinationNumber,
|
|
137
|
-
body: messageBody,
|
|
138
|
-
keyword: messageKeyword,
|
|
139
|
-
messageId: inboundMessageId,
|
|
140
|
-
timestamp,
|
|
141
|
-
},
|
|
142
|
-
}),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
console.log(\`Webhook response: \${response.status}\`);
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.error('Webhook failed:', err.message);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
console.log(\`Processed inbound SMS from \${originationNumber}\`);
|
|
152
|
-
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error('Error processing inbound SMS:', error);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return { statusCode: 200, body: 'OK' };
|
|
159
|
-
};
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
export default handler
|