@bitblit/ratchet-aws 4.0.113-alpha → 4.0.116-alpha
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/lib/cloudwatch/cloud-watch-log-group-ratchet.d.ts +1 -0
- package/lib/cloudwatch/cloud-watch-logs-ratchet.d.ts +1 -0
- package/lib/cloudwatch/cloud-watch-metrics-ratchet.d.ts +1 -0
- package/lib/daemon/daemon-like.d.ts +1 -1
- package/lib/daemon/daemon-util.d.ts +2 -2
- package/lib/daemon/daemon.d.ts +1 -1
- package/lib/dynamodb/dynamo-ratchet.d.ts +1 -0
- package/lib/ec2/ec2-ratchet.d.ts +4 -2
- package/lib/index.d.ts +78 -1
- package/lib/index.mjs +10 -0
- package/lib/index.mjs.map +1 -0
- package/lib/route53/route-53-ratchet.d.ts +1 -0
- package/lib/s3/s3-cache-ratchet.d.ts +3 -2
- package/lib/s3/s3-cache-to-local-disk-ratchet.d.ts +1 -1
- package/lib/ses/mailer.d.ts +1 -0
- package/lib/sns/sns-ratchet.d.ts +1 -0
- package/package.json +25 -84
- package/lib/batch/aws-batch-background-processor.js +0 -45
- package/lib/batch/aws-batch-background-processor.spec.js +0 -15
- package/lib/batch/aws-batch-ratchet.js +0 -55
- package/lib/batch/aws-batch-ratchet.spec.js +0 -33
- package/lib/build/ratchet-aws-info.js +0 -14
- package/lib/cache/dynamo-db-storage-provider.js +0 -109
- package/lib/cache/s3-storage-provider.js +0 -43
- package/lib/cache/simple-cache-object-wrapper.js +0 -1
- package/lib/cache/simple-cache-read-options.js +0 -1
- package/lib/cache/simple-cache-storage-provider.js +0 -1
- package/lib/cache/simple-cache.js +0 -64
- package/lib/cache/simple-cache.spec.js +0 -67
- package/lib/cloudwatch/cloud-watch-log-group-ratchet.js +0 -71
- package/lib/cloudwatch/cloud-watch-log-group-ratchet.spec.js +0 -19
- package/lib/cloudwatch/cloud-watch-logs-ratchet.js +0 -170
- package/lib/cloudwatch/cloud-watch-logs-ratchet.spec.js +0 -84
- package/lib/cloudwatch/cloud-watch-metrics-ratchet.js +0 -54
- package/lib/cloudwatch/cloud-watch-metrics-ratchet.spec.js +0 -23
- package/lib/daemon/daemon-like.js +0 -1
- package/lib/daemon/daemon-process-create-options.js +0 -1
- package/lib/daemon/daemon-process-state-public-token.js +0 -1
- package/lib/daemon/daemon-process-state.js +0 -1
- package/lib/daemon/daemon-util.js +0 -148
- package/lib/daemon/daemon-util.spec.js +0 -79
- package/lib/daemon/daemon.js +0 -128
- package/lib/dao/prototype-dao-config.js +0 -1
- package/lib/dao/prototype-dao-db.js +0 -1
- package/lib/dao/prototype-dao-provider.js +0 -1
- package/lib/dao/prototype-dao.js +0 -88
- package/lib/dao/prototype-dao.spec.js +0 -26
- package/lib/dao/s3-prototype-dao-provider.js +0 -26
- package/lib/dao/s3-simple-dao.js +0 -76
- package/lib/dao/simple-dao-item.js +0 -1
- package/lib/dynamodb/dynamo-ratchet-like.js +0 -1
- package/lib/dynamodb/dynamo-ratchet.js +0 -666
- package/lib/dynamodb/dynamo-ratchet.spec.js +0 -156
- package/lib/dynamodb/dynamo-table-ratchet.js +0 -88
- package/lib/dynamodb/hash-spreader.js +0 -65
- package/lib/dynamodb/hash-spreader.spec.js +0 -17
- package/lib/ec2/ec2-ratchet.js +0 -107
- package/lib/ec2/ec2-ratchet.spec.js +0 -31
- package/lib/environment/cascade-environment-service-provider.js +0 -24
- package/lib/environment/env-var-environment-service-provider.js +0 -30
- package/lib/environment/environment-service-config.js +0 -1
- package/lib/environment/environment-service-provider.js +0 -1
- package/lib/environment/environment-service.js +0 -50
- package/lib/environment/environment-service.spec.js +0 -22
- package/lib/environment/fixed-environment-service-provider.js +0 -21
- package/lib/environment/s3-environment-service-provider.js +0 -27
- package/lib/environment/ssm-environment-service-provider.js +0 -59
- package/lib/expiring-code/dynamo-expiring-code-provider.js +0 -24
- package/lib/expiring-code/expiring-code-params.js +0 -1
- package/lib/expiring-code/expiring-code-provider.js +0 -1
- package/lib/expiring-code/expiring-code-ratchet.js +0 -34
- package/lib/expiring-code/expiring-code-ratchet.spec.js +0 -7
- package/lib/expiring-code/expiring-code.js +0 -1
- package/lib/expiring-code/s3-expiring-code-provider.js +0 -48
- package/lib/expiring-code/s3-expiring-code-provider.spec.js +0 -46
- package/lib/iam/aws-credentials-ratchet.js +0 -18
- package/lib/index.js +0 -1
- package/lib/lambda/lambda-event-detector.js +0 -38
- package/lib/lambda/lambda-event-type-guards.js +0 -24
- package/lib/model/cloud-watch-metrics-minute-level-dynamo-count-request.js +0 -1
- package/lib/model/cloud-watch-metrics-unit.js +0 -30
- package/lib/model/dynamo/doc-put-item-command-input.js +0 -1
- package/lib/model/dynamo/doc-query-command-input.js +0 -1
- package/lib/model/dynamo/doc-scan-command-input.js +0 -1
- package/lib/model/dynamo/doc-update-item-command-input.js +0 -1
- package/lib/model/dynamo-count-result.js +0 -1
- package/lib/route53/route-53-ratchet.js +0 -55
- package/lib/runtime-parameter/cached-stored-runtime-parameter.js +0 -1
- package/lib/runtime-parameter/dynamo-runtime-parameter-provider.js +0 -36
- package/lib/runtime-parameter/dynamo-runtime-parameter-provider.spec.js +0 -49
- package/lib/runtime-parameter/global-variable-override-runtime-parameter-provider.js +0 -51
- package/lib/runtime-parameter/global-variable-override-runtime-parameter-provider.spec.js +0 -37
- package/lib/runtime-parameter/memory-runtime-parameter-provider.js +0 -27
- package/lib/runtime-parameter/runtime-parameter-provider.js +0 -1
- package/lib/runtime-parameter/runtime-parameter-ratchet.js +0 -71
- package/lib/runtime-parameter/runtime-parameter-ratchet.spec.js +0 -39
- package/lib/runtime-parameter/stored-runtime-parameter.js +0 -1
- package/lib/s3/s3-cache-ratchet.js +0 -330
- package/lib/s3/s3-cache-ratchet.spec.js +0 -97
- package/lib/s3/s3-cache-to-local-disk-ratchet.js +0 -105
- package/lib/s3/s3-cache-to-local-dist-ratchet.spec.js +0 -22
- package/lib/s3/s3-location-sync-ratchet.js +0 -140
- package/lib/s3/s3-ratchet.js +0 -22
- package/lib/s3/s3-ratchet.spec.js +0 -20
- package/lib/ses/email-attachment.js +0 -1
- package/lib/ses/mailer-config.js +0 -1
- package/lib/ses/mailer-like.js +0 -1
- package/lib/ses/mailer.js +0 -206
- package/lib/ses/mailer.spec.js +0 -104
- package/lib/ses/ratchet-template-renderer.js +0 -1
- package/lib/ses/ready-to-send-email.js +0 -1
- package/lib/ses/remote-handlebars-template-renderer.js +0 -78
- package/lib/ses/resolved-ready-to-send-email.js +0 -1
- package/lib/sns/sns-ratchet.js +0 -45
- package/lib/sns/sns-ratchet.spec.js +0 -17
- package/lib/sync-lock/dynamo-db-sync-lock.js +0 -69
- package/lib/sync-lock/dynamo-db-sync-lock.spec.js +0 -30
- package/lib/sync-lock/memory-sync-lock.js +0 -35
- package/lib/sync-lock/sync-lock-provider.js +0 -1
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { CopyObjectCommand, GetObjectCommand, ListObjectsV2Command, } from '@aws-sdk/client-s3';
|
|
2
|
-
import _ from 'lodash';
|
|
3
|
-
import { Logger } from '@bitblit/ratchet-common/lib/logger/logger.js';
|
|
4
|
-
import { RequireRatchet } from '@bitblit/ratchet-common/lib/lang/require-ratchet.js';
|
|
5
|
-
import { PromiseRatchet } from '@bitblit/ratchet-common/lib/lang/promise-ratchet.js';
|
|
6
|
-
import { Upload } from '@aws-sdk/lib-storage';
|
|
7
|
-
export class S3LocationSyncRatchet {
|
|
8
|
-
config;
|
|
9
|
-
constructor(config) {
|
|
10
|
-
RequireRatchet.notNullOrUndefined(config, 'config');
|
|
11
|
-
this.config = config;
|
|
12
|
-
if (!this.config.maxNumThreads) {
|
|
13
|
-
this.config.maxNumThreads = 15;
|
|
14
|
-
}
|
|
15
|
-
if (!this.config.maxRetries) {
|
|
16
|
-
this.config.maxRetries = 5;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
updateSrcPrefix(prefix) {
|
|
20
|
-
this.config.srcPrefix = prefix;
|
|
21
|
-
}
|
|
22
|
-
updateDstPrefix(prefix) {
|
|
23
|
-
this.config.dstPrefix = prefix;
|
|
24
|
-
}
|
|
25
|
-
async copyObject(key, size, express = false) {
|
|
26
|
-
const dstKey = key.replace(this.config.srcPrefix, this.config.dstPrefix);
|
|
27
|
-
let completedCopying = false;
|
|
28
|
-
let retries = 0;
|
|
29
|
-
while (!completedCopying && retries < this.config.maxRetries) {
|
|
30
|
-
Logger.debug(`${retries > 0 ? `Retry ${retries} ` : ''}${express ? 'Express' : 'Slow'} copying
|
|
31
|
-
[${[this.config.srcBucket, key].join('/')} ---> ${[this.config.dstBucket, dstKey].join('/')}]`);
|
|
32
|
-
try {
|
|
33
|
-
if (express) {
|
|
34
|
-
const params = {
|
|
35
|
-
CopySource: encodeURIComponent([this.config.srcBucket, key].join('/')),
|
|
36
|
-
Bucket: this.config.dstBucket,
|
|
37
|
-
Key: dstKey,
|
|
38
|
-
MetadataDirective: 'COPY',
|
|
39
|
-
};
|
|
40
|
-
await this.config.dstS3.send(new CopyObjectCommand(params));
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
const fetched = await this.config.srcS3.send(new GetObjectCommand({ Bucket: this.config.srcBucket, Key: key }));
|
|
44
|
-
const params = {
|
|
45
|
-
Bucket: this.config.dstBucket,
|
|
46
|
-
Key: dstKey,
|
|
47
|
-
Body: fetched.Body,
|
|
48
|
-
ContentLength: size,
|
|
49
|
-
};
|
|
50
|
-
const upload = new Upload({
|
|
51
|
-
client: this.config.dstS3,
|
|
52
|
-
params: params,
|
|
53
|
-
tags: [],
|
|
54
|
-
queueSize: 4,
|
|
55
|
-
partSize: 1024 * 1024 * 5,
|
|
56
|
-
leavePartsOnError: false,
|
|
57
|
-
});
|
|
58
|
-
upload.on('httpUploadProgress', (progress) => {
|
|
59
|
-
Logger.info('Uploading : %s', progress);
|
|
60
|
-
});
|
|
61
|
-
await upload.done();
|
|
62
|
-
}
|
|
63
|
-
completedCopying = true;
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
Logger.warn(`Can't ${express ? 'express' : 'slow'} copy
|
|
67
|
-
[${[this.config.srcBucket, key].join('/')} ---> ${[this.config.dstBucket, dstKey].join('/')}]: %j`, err);
|
|
68
|
-
retries++;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
Logger.debug(`Finished ${express ? 'express' : 'slow'} copying
|
|
72
|
-
[${[this.config.srcBucket, key].join('/')} ---> ${[this.config.dstBucket, dstKey].join('/')}]`);
|
|
73
|
-
}
|
|
74
|
-
async listObjects(bucket, prefix, s3) {
|
|
75
|
-
Logger.info(`Scanning bucket [${[bucket, prefix].join('/')}]`);
|
|
76
|
-
const params = {
|
|
77
|
-
Bucket: bucket,
|
|
78
|
-
Prefix: prefix,
|
|
79
|
-
};
|
|
80
|
-
let more = true;
|
|
81
|
-
const rval = {};
|
|
82
|
-
while (more) {
|
|
83
|
-
const response = await s3.send(new ListObjectsV2Command(params));
|
|
84
|
-
more = response.IsTruncated;
|
|
85
|
-
_.each(response.Contents, (obj) => {
|
|
86
|
-
rval[obj.Key] = { Key: obj.Key, LastModified: obj.LastModified, ETag: obj.ETag, Size: obj.Size };
|
|
87
|
-
});
|
|
88
|
-
if (more) {
|
|
89
|
-
params.ContinuationToken = response.NextContinuationToken;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return rval;
|
|
93
|
-
}
|
|
94
|
-
async startSyncing() {
|
|
95
|
-
Logger.info(`Syncing [${this.config.srcBucket}/${this.config.srcPrefix}
|
|
96
|
-
---> ${this.config.dstBucket}/${this.config.dstPrefix}]`);
|
|
97
|
-
const cp = async (obj) => {
|
|
98
|
-
await this.copyObject(obj.Key, obj.Size);
|
|
99
|
-
};
|
|
100
|
-
let cmpResult = await this.compareSrcAndDst();
|
|
101
|
-
if (cmpResult.needCopy.length > 0 || cmpResult.diff.length > 0) {
|
|
102
|
-
await PromiseRatchet.runBoundedParallelSingleParam(cp, cmpResult.needCopy, this, this.config.maxNumThreads);
|
|
103
|
-
await PromiseRatchet.runBoundedParallelSingleParam(cp, cmpResult.diff, this, this.config.maxNumThreads);
|
|
104
|
-
Logger.info('Verifying...');
|
|
105
|
-
cmpResult = await this.compareSrcAndDst();
|
|
106
|
-
Logger.debug('Compare result %j', cmpResult);
|
|
107
|
-
}
|
|
108
|
-
return cmpResult.needCopy.length === 0 && cmpResult.diff.length === 0;
|
|
109
|
-
}
|
|
110
|
-
async compareSrcAndDst() {
|
|
111
|
-
const getSrc = this.listObjects(this.config.srcBucket, this.config.srcPrefix, this.config.srcS3);
|
|
112
|
-
const getDst = this.listObjects(this.config.dstBucket, this.config.dstPrefix, this.config.dstS3);
|
|
113
|
-
const srcObjs = await getSrc;
|
|
114
|
-
const dstObjs = await getDst;
|
|
115
|
-
const rval = {
|
|
116
|
-
needCopy: [],
|
|
117
|
-
existed: [],
|
|
118
|
-
diff: [],
|
|
119
|
-
};
|
|
120
|
-
await PromiseRatchet.runBoundedParallelSingleParam((key) => {
|
|
121
|
-
const sObj = srcObjs[key];
|
|
122
|
-
const dstKey = key.replace(this.config.srcPrefix, this.config.dstPrefix);
|
|
123
|
-
const dObj = dstObjs.hasOwnProperty(dstKey) ? dstObjs[dstKey] : undefined;
|
|
124
|
-
if (!dObj) {
|
|
125
|
-
rval.needCopy.push(sObj);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (sObj.Size !== dObj.Size) {
|
|
129
|
-
rval.diff.push(sObj);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (sObj.LastModified.getTime() <= dObj.LastModified.getTime()) {
|
|
133
|
-
rval.existed.push(sObj);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
rval.diff.push(sObj);
|
|
137
|
-
}, Object.keys(srcObjs), this, this.config.maxNumThreads);
|
|
138
|
-
return rval;
|
|
139
|
-
}
|
|
140
|
-
}
|
package/lib/s3/s3-ratchet.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { RequireRatchet } from '@bitblit/ratchet-common/lib/lang/require-ratchet.js';
|
|
2
|
-
export class S3Ratchet {
|
|
3
|
-
static checkS3UrlForValidity(value) {
|
|
4
|
-
let rval = false;
|
|
5
|
-
if (value) {
|
|
6
|
-
rval = value.startsWith('s3://') && value.trim().length > 5;
|
|
7
|
-
}
|
|
8
|
-
return rval;
|
|
9
|
-
}
|
|
10
|
-
static extractBucketFromURL(value) {
|
|
11
|
-
RequireRatchet.true(S3Ratchet.checkS3UrlForValidity(value), 'invalid s3 url');
|
|
12
|
-
const idx1 = value.indexOf('/', 5);
|
|
13
|
-
const rval = idx1 > 0 ? value.substring(5, idx1) : value.substring(5);
|
|
14
|
-
return rval;
|
|
15
|
-
}
|
|
16
|
-
static extractKeyFromURL(value) {
|
|
17
|
-
RequireRatchet.true(S3Ratchet.checkS3UrlForValidity(value), 'invalid s3 url');
|
|
18
|
-
const idx1 = value.indexOf('/', 5);
|
|
19
|
-
const rval = idx1 > 0 ? value.substring(idx1 + 1) : null;
|
|
20
|
-
return rval;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
-
import { S3Ratchet } from './s3-ratchet.js';
|
|
3
|
-
import { mockClient } from 'aws-sdk-client-mock';
|
|
4
|
-
let mockS3;
|
|
5
|
-
describe('#S3Ratchet', function () {
|
|
6
|
-
mockS3 = mockClient(S3Client);
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockS3.reset();
|
|
9
|
-
});
|
|
10
|
-
it('should checkS3UrlForValidity', async () => {
|
|
11
|
-
expect(S3Ratchet.checkS3UrlForValidity('s3://test/out/b.txt')).toBeTruthy();
|
|
12
|
-
expect(S3Ratchet.checkS3UrlForValidity('http://test/out/b.txt')).toBeFalsy();
|
|
13
|
-
});
|
|
14
|
-
it('should extractBucketFromURL', async () => {
|
|
15
|
-
expect(S3Ratchet.extractBucketFromURL('s3://test/out/b.txt')).toEqual('test');
|
|
16
|
-
});
|
|
17
|
-
it('should extractKeyFromURL', async () => {
|
|
18
|
-
expect(S3Ratchet.extractKeyFromURL('s3://test/out/b.txt')).toEqual('out/b.txt');
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/ses/mailer-config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/ses/mailer-like.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/ses/mailer.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { ErrorRatchet } from '@bitblit/ratchet-common/lib/lang/error-ratchet.js';
|
|
2
|
-
import { Logger } from '@bitblit/ratchet-common/lib/logger/logger.js';
|
|
3
|
-
import { RequireRatchet } from '@bitblit/ratchet-common/lib/lang/require-ratchet.js';
|
|
4
|
-
import { Base64Ratchet } from '@bitblit/ratchet-common/lib/lang/base64-ratchet.js';
|
|
5
|
-
import { StringRatchet } from '@bitblit/ratchet-common/lib/lang/string-ratchet.js';
|
|
6
|
-
import { SendRawEmailCommand } from '@aws-sdk/client-ses';
|
|
7
|
-
import { DateTime } from 'luxon';
|
|
8
|
-
export class Mailer {
|
|
9
|
-
ses;
|
|
10
|
-
config;
|
|
11
|
-
static EMAIL = new RegExp('.+@.+\\.[a-z]+');
|
|
12
|
-
constructor(ses, config = {}) {
|
|
13
|
-
this.ses = ses;
|
|
14
|
-
this.config = config;
|
|
15
|
-
RequireRatchet.notNullOrUndefined(this.ses);
|
|
16
|
-
if (!!config.archive && !config.archive.getDefaultBucket()) {
|
|
17
|
-
throw new Error('If archive specified, must set a default bucket');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
async fillEmailBody(rts, context, htmlTemplateName, txtTemplateName = null, layoutName = null, partialNames = null) {
|
|
21
|
-
RequireRatchet.notNullOrUndefined(htmlTemplateName);
|
|
22
|
-
if (!this.config.templateRenderer) {
|
|
23
|
-
ErrorRatchet.throwFormattedErr('Cannot use fill body if template renderer not set');
|
|
24
|
-
}
|
|
25
|
-
rts.htmlMessage = await this.config.templateRenderer.renderTemplate(htmlTemplateName, context, layoutName, partialNames);
|
|
26
|
-
rts.txtMessage = !!txtTemplateName ? await this.config.templateRenderer.renderTemplate(txtTemplateName, context) : null;
|
|
27
|
-
return rts;
|
|
28
|
-
}
|
|
29
|
-
async fillEmailBodyAndSend(rts, context, htmlTemplateName, txtTemplateName = null, layoutName = null, partialNames = null) {
|
|
30
|
-
const newVal = await this.fillEmailBody(rts, context, htmlTemplateName, txtTemplateName, layoutName, partialNames);
|
|
31
|
-
const rval = await this.sendEmail(newVal);
|
|
32
|
-
return rval;
|
|
33
|
-
}
|
|
34
|
-
filterEmailsToValid(emails) {
|
|
35
|
-
const rval = (emails || []).filter((e) => {
|
|
36
|
-
if (!this.config.allowedDestinationEmails || this.config.allowedDestinationEmails.length == 0) {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
const match = this.config.allowedDestinationEmails.find((s) => s.test(e));
|
|
41
|
-
return !!match;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
return rval;
|
|
45
|
-
}
|
|
46
|
-
async archiveEmailIfConfigured(rts) {
|
|
47
|
-
let rval = false;
|
|
48
|
-
if (!!rts && !!this.config.archive && !rts.doNotArchive) {
|
|
49
|
-
Logger.debug('Archiving outbound email to : %j', rts.destinationAddresses);
|
|
50
|
-
let targetPath = StringRatchet.trimToEmpty(this.config.archivePrefix);
|
|
51
|
-
if (!targetPath.endsWith('/')) {
|
|
52
|
-
targetPath += '/';
|
|
53
|
-
}
|
|
54
|
-
const now = DateTime.utc();
|
|
55
|
-
targetPath +=
|
|
56
|
-
'year=' +
|
|
57
|
-
now.toFormat('yyyy') +
|
|
58
|
-
'/month=' +
|
|
59
|
-
now.toFormat('MM') +
|
|
60
|
-
'/day=' +
|
|
61
|
-
now.toFormat('dd') +
|
|
62
|
-
'/hour=' +
|
|
63
|
-
now.toFormat('HH') +
|
|
64
|
-
'/' +
|
|
65
|
-
now.toFormat('mm_ss__SSS');
|
|
66
|
-
targetPath += '.json';
|
|
67
|
-
try {
|
|
68
|
-
await this.config.archive.writeObjectToCacheFile(targetPath, rts);
|
|
69
|
-
rval = true;
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
Logger.warn('Failed to archive email %s %j : %s', targetPath, rts, err);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return rval;
|
|
76
|
-
}
|
|
77
|
-
applyLimitsToBodySizesIfAnyInPlace(rts) {
|
|
78
|
-
if (this.config.maxMessageBodySizeInBytes) {
|
|
79
|
-
const txtSize = StringRatchet.trimToEmpty(rts.txtMessage).length;
|
|
80
|
-
const htmlSize = StringRatchet.trimToEmpty(rts.htmlMessage).length;
|
|
81
|
-
const totalSize = txtSize + htmlSize;
|
|
82
|
-
if (totalSize > this.config.maxMessageBodySizeInBytes) {
|
|
83
|
-
Logger.warn('Max message size is %d but size is %d - converting', this.config.maxMessageBodySizeInBytes, totalSize);
|
|
84
|
-
rts.attachments = rts.attachments || [];
|
|
85
|
-
if (StringRatchet.trimToNull(rts.txtMessage)) {
|
|
86
|
-
const txtAttach = {
|
|
87
|
-
filename: 'original-txt-body.txt',
|
|
88
|
-
contentType: 'text/plain',
|
|
89
|
-
base64Data: Base64Ratchet.generateBase64VersionOfString(rts.txtMessage),
|
|
90
|
-
};
|
|
91
|
-
rts.attachments.push(txtAttach);
|
|
92
|
-
}
|
|
93
|
-
if (StringRatchet.trimToNull(rts.htmlMessage)) {
|
|
94
|
-
const htmlAttach = {
|
|
95
|
-
filename: 'original-html-body.html',
|
|
96
|
-
contentType: 'text/html',
|
|
97
|
-
base64Data: Base64Ratchet.generateBase64VersionOfString(rts.htmlMessage),
|
|
98
|
-
};
|
|
99
|
-
rts.attachments.push(htmlAttach);
|
|
100
|
-
}
|
|
101
|
-
rts.htmlMessage = null;
|
|
102
|
-
rts.txtMessage = 'The message was too large and was converted to attachment(s). Please see attached files for content';
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
applyLimitsToAttachmentSizesIfAnyInPlace(rts) {
|
|
107
|
-
if (this.config.maxAttachmentSizeInBase64Bytes) {
|
|
108
|
-
const filtered = [];
|
|
109
|
-
if (rts.attachments) {
|
|
110
|
-
rts.attachments.forEach((a) => {
|
|
111
|
-
if (a.base64Data && a.base64Data.length < this.config.maxAttachmentSizeInBase64Bytes) {
|
|
112
|
-
filtered.push(a);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
Logger.warn('Removing too-large attachment : %s : %s : %d', a.filename, a.contentType, a.base64Data.length);
|
|
116
|
-
filtered.push({
|
|
117
|
-
filename: 'attachment-removed-notice-' + StringRatchet.createRandomHexString(4) + '.txt',
|
|
118
|
-
contentType: 'text/plain',
|
|
119
|
-
base64Data: Base64Ratchet.generateBase64VersionOfString('Attachment ' +
|
|
120
|
-
a.filename +
|
|
121
|
-
' of type ' +
|
|
122
|
-
a.contentType +
|
|
123
|
-
' was removed since it was ' +
|
|
124
|
-
a.base64Data.length +
|
|
125
|
-
' bytes but max allowed is ' +
|
|
126
|
-
this.config.maxAttachmentSizeInBase64Bytes),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
rts.attachments = filtered;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async sendEmail(inRts) {
|
|
135
|
-
RequireRatchet.notNullOrUndefined(inRts, 'RTS must be defined');
|
|
136
|
-
RequireRatchet.notNullOrUndefined(inRts.destinationAddresses, 'Destination addresses must be defined');
|
|
137
|
-
let rval = null;
|
|
138
|
-
let toAddresses = this.filterEmailsToValid(inRts.destinationAddresses);
|
|
139
|
-
const autoBcc = inRts.doNotAutoBcc ? [] : this.config.autoBccAddresses || [];
|
|
140
|
-
const bccAddresses = (inRts.bccAddresses || []).concat(autoBcc);
|
|
141
|
-
if (toAddresses.length === 0 && bccAddresses.length > 0) {
|
|
142
|
-
Logger.debug('Destination emails filtered to none but BCC defined, copying BCC');
|
|
143
|
-
toAddresses = bccAddresses;
|
|
144
|
-
}
|
|
145
|
-
const rts = Object.assign({}, inRts);
|
|
146
|
-
rts.srcDestinationAddresses = inRts.destinationAddresses;
|
|
147
|
-
rts.srcBccAddresses = inRts.bccAddresses;
|
|
148
|
-
rts.destinationAddresses = toAddresses;
|
|
149
|
-
rts.bccAddresses = bccAddresses;
|
|
150
|
-
this.applyLimitsToBodySizesIfAnyInPlace(rts);
|
|
151
|
-
this.applyLimitsToAttachmentSizesIfAnyInPlace(rts);
|
|
152
|
-
await this.archiveEmailIfConfigured(rts);
|
|
153
|
-
if (rts.destinationAddresses.length === 0) {
|
|
154
|
-
Logger.info('After cleaning email lists, no destination addresses left - not sending email');
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
const toLine = 'To: ' + rts.destinationAddresses.join(', ') + '\n';
|
|
158
|
-
const bccLine = !!rts.bccAddresses && rts.bccAddresses.length > 0 ? 'Bcc: ' + rts.bccAddresses.join(', ') + '\n' : '';
|
|
159
|
-
try {
|
|
160
|
-
const from = rts.fromAddress || this.config.defaultSendingAddress;
|
|
161
|
-
const boundary = 'NextPart';
|
|
162
|
-
const altBoundary = 'AltPart';
|
|
163
|
-
let rawMail = 'From: ' + from + '\n';
|
|
164
|
-
rawMail += toLine;
|
|
165
|
-
rawMail += bccLine;
|
|
166
|
-
rawMail += 'Subject: ' + rts.subject + '\n';
|
|
167
|
-
rawMail += 'MIME-Version: 1.0\n';
|
|
168
|
-
rawMail += 'Content-Type: multipart/mixed; boundary="' + boundary + '"\n';
|
|
169
|
-
rawMail += '\n\n--' + boundary + '\n';
|
|
170
|
-
rawMail += 'Content-Type: multipart/alternative; boundary="' + altBoundary + '"\n';
|
|
171
|
-
if (!!StringRatchet.trimToNull(rts.htmlMessage)) {
|
|
172
|
-
rawMail += '\n\n--' + altBoundary + '\n';
|
|
173
|
-
rawMail += 'Content-Type: text/html; charset="UTF-8"\n\n';
|
|
174
|
-
rawMail += rts.htmlMessage;
|
|
175
|
-
}
|
|
176
|
-
if (!!StringRatchet.trimToNull(rts.txtMessage)) {
|
|
177
|
-
rawMail += '\n\n--' + altBoundary + '\n';
|
|
178
|
-
rawMail += 'Content-Type: text/plain\n\n';
|
|
179
|
-
rawMail += rts.txtMessage;
|
|
180
|
-
}
|
|
181
|
-
rawMail += '\n\n--' + altBoundary + '--\n';
|
|
182
|
-
if (rts.attachments) {
|
|
183
|
-
rts.attachments.forEach((a) => {
|
|
184
|
-
rawMail += '\n\n--' + boundary + '\n';
|
|
185
|
-
rawMail += 'Content-Type: ' + a.contentType + '; name="' + a.filename + '"\n';
|
|
186
|
-
rawMail += 'Content-Transfer-Encoding: base64\n';
|
|
187
|
-
rawMail += 'Content-Disposition: attachment\n\n';
|
|
188
|
-
rawMail += a.base64Data.replace(/([^\0]{76})/g, '$1\n') + '\n\n';
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
rawMail += '\n\n--' + boundary + '--\n';
|
|
192
|
-
const params = {
|
|
193
|
-
RawMessage: { Data: new TextEncoder().encode(rawMail) },
|
|
194
|
-
};
|
|
195
|
-
rval = await this.ses.send(new SendRawEmailCommand(params));
|
|
196
|
-
}
|
|
197
|
-
catch (err) {
|
|
198
|
-
Logger.error('Error while processing email: %s', err, err);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return rval;
|
|
202
|
-
}
|
|
203
|
-
static validEmail(email) {
|
|
204
|
-
return email !== null && Mailer.EMAIL.test(email);
|
|
205
|
-
}
|
|
206
|
-
}
|
package/lib/ses/mailer.spec.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { SendRawEmailCommand, SESClient } from '@aws-sdk/client-ses';
|
|
2
|
-
import { Mailer } from './mailer.js';
|
|
3
|
-
import { StringRatchet } from '@bitblit/ratchet-common/lib/lang/string-ratchet.js';
|
|
4
|
-
import { Base64Ratchet } from '@bitblit/ratchet-common/lib/lang/base64-ratchet.js';
|
|
5
|
-
import { mockClient } from 'aws-sdk-client-mock';
|
|
6
|
-
let mockSES;
|
|
7
|
-
const smallImageBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
|
|
8
|
-
describe('#mailer', function () {
|
|
9
|
-
mockSES = mockClient(SESClient);
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockSES.reset();
|
|
12
|
-
});
|
|
13
|
-
it('should send email', async () => {
|
|
14
|
-
const config = {
|
|
15
|
-
defaultSendingAddress: 'test1@test.com',
|
|
16
|
-
autoBccAddresses: [],
|
|
17
|
-
archive: null,
|
|
18
|
-
archivePrefix: null,
|
|
19
|
-
};
|
|
20
|
-
const svc = new Mailer(mockSES, config);
|
|
21
|
-
const attach1 = {
|
|
22
|
-
filename: 'test.txt',
|
|
23
|
-
contentType: 'text/plain',
|
|
24
|
-
base64Data: Base64Ratchet.generateBase64VersionOfString('This is a test2'),
|
|
25
|
-
};
|
|
26
|
-
const attach2 = {
|
|
27
|
-
filename: 'a2.png',
|
|
28
|
-
contentType: 'image/png',
|
|
29
|
-
base64Data: smallImageBase64,
|
|
30
|
-
};
|
|
31
|
-
const rts = {
|
|
32
|
-
txtMessage: 'test txt',
|
|
33
|
-
htmlMessage: '<h1>Test html</h1><p>Test paragraph</p>',
|
|
34
|
-
subject: 'Test subject',
|
|
35
|
-
fromAddress: 'test@test.com',
|
|
36
|
-
destinationAddresses: ['testout@test.com'],
|
|
37
|
-
attachments: [attach1, attach2],
|
|
38
|
-
};
|
|
39
|
-
mockSES.on(SendRawEmailCommand).resolves({});
|
|
40
|
-
const result = await svc.sendEmail(rts);
|
|
41
|
-
expect(result).toBeTruthy();
|
|
42
|
-
});
|
|
43
|
-
it('should allow for unicode in email subject', async () => {
|
|
44
|
-
const config = {
|
|
45
|
-
defaultSendingAddress: 'jflint@adomni.com',
|
|
46
|
-
autoBccAddresses: [],
|
|
47
|
-
archive: null,
|
|
48
|
-
archivePrefix: null,
|
|
49
|
-
};
|
|
50
|
-
const svc = new Mailer(mockSES, config);
|
|
51
|
-
const rts = {
|
|
52
|
-
txtMessage: 'test txt',
|
|
53
|
-
htmlMessage: '<h1>Test html</h1><p>Test paragraph</p>',
|
|
54
|
-
subject: "Rappel: Votre panneau d'affichage Shout est diffusé aujourd'hui!",
|
|
55
|
-
fromAddress: 'jflint@adomni.com',
|
|
56
|
-
destinationAddresses: ['jflint@adomni.com'],
|
|
57
|
-
};
|
|
58
|
-
mockSES.on(SendRawEmailCommand).resolves({});
|
|
59
|
-
const result = await svc.sendEmail(rts);
|
|
60
|
-
expect(result).toBeTruthy();
|
|
61
|
-
});
|
|
62
|
-
it('should filter outbound', async () => {
|
|
63
|
-
const config = {
|
|
64
|
-
allowedDestinationEmails: [/.*test\.com/, /.*.test2\.com/],
|
|
65
|
-
};
|
|
66
|
-
const svc = new Mailer({}, config);
|
|
67
|
-
const out1 = ['a@test.com', 'b@fail.com'];
|
|
68
|
-
const res1 = svc.filterEmailsToValid(out1);
|
|
69
|
-
expect(res1).toBeTruthy();
|
|
70
|
-
expect(res1.length).toEqual(1);
|
|
71
|
-
const out2 = ['a@fail.com', 'b@fail.com'];
|
|
72
|
-
const res2 = svc.filterEmailsToValid(out2);
|
|
73
|
-
expect(res2).toBeTruthy();
|
|
74
|
-
expect(res2.length).toEqual(0);
|
|
75
|
-
});
|
|
76
|
-
it('should fix a huge text/html body', async () => {
|
|
77
|
-
const config = {
|
|
78
|
-
defaultSendingAddress: 'test@test.com',
|
|
79
|
-
autoBccAddresses: [],
|
|
80
|
-
archive: null,
|
|
81
|
-
archivePrefix: null,
|
|
82
|
-
maxMessageBodySizeInBytes: 500,
|
|
83
|
-
maxAttachmentSizeInBase64Bytes: 1000,
|
|
84
|
-
};
|
|
85
|
-
const svc = new Mailer(mockSES, config);
|
|
86
|
-
const bigBody = StringRatchet.createRandomHexString(300);
|
|
87
|
-
const bigAttach = {
|
|
88
|
-
filename: 'test.txt',
|
|
89
|
-
contentType: 'text/plain',
|
|
90
|
-
base64Data: Base64Ratchet.generateBase64VersionOfString(StringRatchet.createRandomHexString(2000)),
|
|
91
|
-
};
|
|
92
|
-
const rts = {
|
|
93
|
-
txtMessage: bigBody,
|
|
94
|
-
htmlMessage: bigBody,
|
|
95
|
-
subject: 'Test big message',
|
|
96
|
-
fromAddress: 'test@test.com',
|
|
97
|
-
destinationAddresses: ['test@test.com'],
|
|
98
|
-
attachments: [bigAttach],
|
|
99
|
-
};
|
|
100
|
-
mockSES.on(SendRawEmailCommand).resolves({});
|
|
101
|
-
const result = await svc.sendEmail(rts);
|
|
102
|
-
expect(result).toBeTruthy();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import handlebars from 'handlebars';
|
|
2
|
-
import fetch from 'cross-fetch';
|
|
3
|
-
import { StringRatchet } from '@bitblit/ratchet-common/lib/lang/string-ratchet.js';
|
|
4
|
-
import { Logger } from '@bitblit/ratchet-common/lib/logger/logger.js';
|
|
5
|
-
import layouts from 'handlebars-layouts';
|
|
6
|
-
export class RemoteHandlebarsTemplateRenderer {
|
|
7
|
-
prefix;
|
|
8
|
-
suffix;
|
|
9
|
-
maxCacheTemplates;
|
|
10
|
-
cache;
|
|
11
|
-
constructor(prefix = '', suffix = '', maxCacheTemplates = 10) {
|
|
12
|
-
this.prefix = prefix;
|
|
13
|
-
this.suffix = suffix;
|
|
14
|
-
this.maxCacheTemplates = maxCacheTemplates;
|
|
15
|
-
if (this.maxCacheTemplates > 0) {
|
|
16
|
-
this.cache = new Map();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
async renderTemplate(templateName, context, layoutName = null) {
|
|
20
|
-
return this.renderRemoteTemplate(templateName, context, layoutName);
|
|
21
|
-
}
|
|
22
|
-
async renderRemoteTemplate(templateName, inContext, layoutName = null) {
|
|
23
|
-
const template = await this.fetchTemplate(templateName);
|
|
24
|
-
const layoutText = !!layoutName ? await this.fetchTemplateText(layoutName) : null;
|
|
25
|
-
if (!!layoutText) {
|
|
26
|
-
await layouts.register(handlebars);
|
|
27
|
-
handlebars.registerPartial(layoutName, layoutText);
|
|
28
|
-
}
|
|
29
|
-
const context = inContext || {};
|
|
30
|
-
const result = !!template ? template(context) : null;
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
async renderTemplateDirect(templateText, context, layoutName = null) {
|
|
34
|
-
const template = handlebars.compile(templateText);
|
|
35
|
-
const result = template(context);
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
async fetchTemplate(templateName) {
|
|
39
|
-
let rval = null;
|
|
40
|
-
try {
|
|
41
|
-
if (!!this.cache && this.cache.has(templateName)) {
|
|
42
|
-
Logger.silly('Cache hit for template : %s', templateName);
|
|
43
|
-
rval = this.cache.get(templateName);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
Logger.debug('Cache miss for template : %s', templateName);
|
|
47
|
-
const templateText = await this.fetchTemplateText(templateName);
|
|
48
|
-
if (!!templateText) {
|
|
49
|
-
rval = handlebars.compile(templateText);
|
|
50
|
-
if (!!this.cache && !!rval) {
|
|
51
|
-
this.cache.set(templateName, rval);
|
|
52
|
-
if (this.cache.size > this.maxCacheTemplates) {
|
|
53
|
-
this.cache.delete(Array.from(this.cache.keys())[0]);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
Logger.warn('Could not fetch and compile template : %s : %s', templateName, err, err);
|
|
61
|
-
rval = null;
|
|
62
|
-
}
|
|
63
|
-
return rval;
|
|
64
|
-
}
|
|
65
|
-
async fetchTemplateText(templateName) {
|
|
66
|
-
let rval = null;
|
|
67
|
-
const url = StringRatchet.trimToEmpty(this.prefix) + templateName + StringRatchet.trimToEmpty(this.suffix);
|
|
68
|
-
try {
|
|
69
|
-
const resp = await fetch(url);
|
|
70
|
-
rval = await resp.text();
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
Logger.warn('Could not fetch url : %s : %s', url, err, err);
|
|
74
|
-
rval = null;
|
|
75
|
-
}
|
|
76
|
-
return rval;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/sns/sns-ratchet.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Logger } from '@bitblit/ratchet-common/lib/logger/logger.js';
|
|
2
|
-
import { RequireRatchet } from '@bitblit/ratchet-common/lib/lang/require-ratchet.js';
|
|
3
|
-
import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';
|
|
4
|
-
export class SnsRatchet {
|
|
5
|
-
sns;
|
|
6
|
-
topicArn;
|
|
7
|
-
constructor(sns = new SNSClient({ region: 'us-east-1' }), topicArn) {
|
|
8
|
-
this.sns = sns;
|
|
9
|
-
this.topicArn = topicArn;
|
|
10
|
-
RequireRatchet.notNullOrUndefined(this.sns, 'sns');
|
|
11
|
-
RequireRatchet.notNullOrUndefined(this.topicArn, 'topicArn');
|
|
12
|
-
}
|
|
13
|
-
async sendMessage(inMsg, suppressErrors = false) {
|
|
14
|
-
let result = null;
|
|
15
|
-
try {
|
|
16
|
-
const safeInMsg = inMsg ? inMsg : 'NO-MESSAGE-PROVIDED';
|
|
17
|
-
const msg = typeof safeInMsg === 'string' ? safeInMsg : JSON.stringify(safeInMsg);
|
|
18
|
-
const params = {
|
|
19
|
-
TopicArn: this.topicArn,
|
|
20
|
-
Message: msg,
|
|
21
|
-
};
|
|
22
|
-
Logger.debug('Sending via SNS : %j', params);
|
|
23
|
-
result = await this.sns.send(new PublishCommand(params));
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
if (suppressErrors) {
|
|
27
|
-
Logger.error('Failed to fire SNS notification : %j : %s', inMsg, err);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
async conditionallySendMessage(inMsg, condition, suppressErrors = false) {
|
|
36
|
-
let rval = null;
|
|
37
|
-
if (condition) {
|
|
38
|
-
rval = await this.sendMessage(inMsg, suppressErrors);
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
Logger.info('Not sending message, condition was false : %j', inMsg);
|
|
42
|
-
}
|
|
43
|
-
return rval;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { PublishCommand, SNSClient } from '@aws-sdk/client-sns';
|
|
2
|
-
import { SnsRatchet } from './sns-ratchet.js';
|
|
3
|
-
import { mockClient } from 'aws-sdk-client-mock';
|
|
4
|
-
let mockSNS;
|
|
5
|
-
describe('#SNSRatchet', function () {
|
|
6
|
-
mockSNS = mockClient(SNSClient);
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockSNS.reset();
|
|
9
|
-
});
|
|
10
|
-
it('should send a message', async () => {
|
|
11
|
-
mockSNS.on(PublishCommand).resolves({});
|
|
12
|
-
const topicArn = 'TOPIC-ARN-HERE';
|
|
13
|
-
const ratchet = new SnsRatchet(mockSNS, topicArn);
|
|
14
|
-
const out = await ratchet.sendMessage('test \n\n' + new Date() + '\n\n---\n\nTest CR');
|
|
15
|
-
expect(out).toBeTruthy();
|
|
16
|
-
});
|
|
17
|
-
});
|