@bitblit/ratchet-common 6.0.145-alpha → 6.0.147-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/package.json +2 -1
- package/src/2d/line-2d.ts +6 -0
- package/src/2d/matrix-factory.ts +94 -0
- package/src/2d/plane-2d-type.ts +6 -0
- package/src/2d/plane-2d.ts +7 -0
- package/src/2d/point-2d.ts +4 -0
- package/src/2d/poly-line-2d.ts +5 -0
- package/src/2d/ratchet-2d.spec.ts +205 -0
- package/src/2d/ratchet-2d.ts +350 -0
- package/src/2d/transformation-matrix.ts +19 -0
- package/src/build/build-information.ts +8 -0
- package/src/build/ratchet-common-info.ts +19 -0
- package/src/histogram/histogram-entry.ts +4 -0
- package/src/histogram/histogram.spec.ts +25 -0
- package/src/histogram/histogram.ts +61 -0
- package/src/jwt/common-jwt-token.ts +17 -0
- package/src/jwt/expired-jwt-handling.ts +5 -0
- package/src/jwt/jwt-decode-only-ratchet.ts +26 -0
- package/src/jwt/jwt-payload-expiration-ratchet.ts +45 -0
- package/src/jwt/jwt-token-base.ts +14 -0
- package/src/lang/array-ratchet.spec.ts +79 -0
- package/src/lang/array-ratchet.ts +141 -0
- package/src/lang/base64-ratchet.spec.ts +48 -0
- package/src/lang/base64-ratchet.ts +247 -0
- package/src/lang/boolean-ratchet.spec.ts +95 -0
- package/src/lang/boolean-ratchet.ts +52 -0
- package/src/lang/composite-last-success-provider.spec.ts +31 -0
- package/src/lang/composite-last-success-provider.ts +30 -0
- package/src/lang/currency-ratchet.ts +29 -0
- package/src/lang/date-ratchet.spec.ts +27 -0
- package/src/lang/date-ratchet.ts +42 -0
- package/src/lang/duration-ratchet.spec.ts +47 -0
- package/src/lang/duration-ratchet.ts +77 -0
- package/src/lang/enum-ratchet.spec.ts +45 -0
- package/src/lang/enum-ratchet.ts +41 -0
- package/src/lang/error-handling-approach.ts +6 -0
- package/src/lang/error-ratchet.spec.ts +25 -0
- package/src/lang/error-ratchet.ts +70 -0
- package/src/lang/esm-ratchet.ts +81 -0
- package/src/lang/expiring-object.spec.ts +56 -0
- package/src/lang/expiring-object.ts +84 -0
- package/src/lang/geolocation-ratchet.spec.ts +177 -0
- package/src/lang/geolocation-ratchet.ts +341 -0
- package/src/lang/global-ratchet.spec.ts +17 -0
- package/src/lang/global-ratchet.ts +105 -0
- package/src/lang/key-value.ts +8 -0
- package/src/lang/last-success-provider.ts +4 -0
- package/src/lang/map-ratchet.spec.ts +113 -0
- package/src/lang/map-ratchet.ts +220 -0
- package/src/lang/no.spec.ts +9 -0
- package/src/lang/no.ts +7 -0
- package/src/lang/number-ratchet.spec.ts +154 -0
- package/src/lang/number-ratchet.ts +253 -0
- package/src/lang/parsed-url.ts +10 -0
- package/src/lang/promise-ratchet.spec.ts +104 -0
- package/src/lang/promise-ratchet.ts +196 -0
- package/src/lang/range.ts +4 -0
- package/src/lang/require-ratchet.spec.ts +85 -0
- package/src/lang/require-ratchet.ts +68 -0
- package/src/lang/simple-arg-ratchet.spec.ts +13 -0
- package/src/lang/simple-arg-ratchet.ts +47 -0
- package/src/lang/simple-encryption-ratchet.ts +88 -0
- package/src/lang/sort-ratchet.spec.ts +58 -0
- package/src/lang/sort-ratchet.ts +50 -0
- package/src/lang/stop-watch.spec.ts +53 -0
- package/src/lang/stop-watch.ts +202 -0
- package/src/lang/string-ratchet.spec.ts +226 -0
- package/src/lang/string-ratchet.ts +676 -0
- package/src/lang/time-zone-ratchet.spec.ts +51 -0
- package/src/lang/time-zone-ratchet.ts +148 -0
- package/src/lang/timeout-token.spec.ts +12 -0
- package/src/lang/timeout-token.ts +21 -0
- package/src/lang/uint-8-array-ratchet.spec.ts +22 -0
- package/src/lang/uint-8-array-ratchet.ts +48 -0
- package/src/lang/web-stream-ratchet.spec.ts +12 -0
- package/src/lang/web-stream-ratchet.ts +96 -0
- package/src/logger/classic-single-line-log-message-formatter.ts +19 -0
- package/src/logger/log-message-builder.ts +60 -0
- package/src/logger/log-message-format-type.ts +11 -0
- package/src/logger/log-message-formatter.ts +6 -0
- package/src/logger/log-message-processor.ts +6 -0
- package/src/logger/log-message.ts +9 -0
- package/src/logger/log-snapshot.ts +6 -0
- package/src/logger/logger-instance.ts +269 -0
- package/src/logger/logger-level-name.ts +11 -0
- package/src/logger/logger-meta.ts +7 -0
- package/src/logger/logger-options.ts +14 -0
- package/src/logger/logger-output-function.ts +10 -0
- package/src/logger/logger-ring-buffer.ts +89 -0
- package/src/logger/logger-util.spec.ts +11 -0
- package/src/logger/logger-util.ts +68 -0
- package/src/logger/logger.spec.ts +177 -0
- package/src/logger/logger.ts +213 -0
- package/src/logger/none-log-message-formatter.ts +10 -0
- package/src/logger/single-line-no-level-log-message-formatter.ts +18 -0
- package/src/logger/structured-json-log-message-formatter.ts +25 -0
- package/src/mail/archive-email-result.ts +8 -0
- package/src/mail/email-attachment.ts +23 -0
- package/src/mail/mail-sending-provider.ts +21 -0
- package/src/mail/mailer-config.ts +30 -0
- package/src/mail/mailer-like.ts +38 -0
- package/src/mail/mailer-util.ts +65 -0
- package/src/mail/mailer.spec.ts +120 -0
- package/src/mail/mailer.ts +214 -0
- package/src/mail/ready-to-send-email.ts +67 -0
- package/src/mail/resolved-ready-to-send-email.ts +17 -0
- package/src/mail/send-email-result.ts +16 -0
- package/src/mail/test-mail-sending-provider.ts +35 -0
- package/src/network/browser-local-ip-provider.spec.ts +23 -0
- package/src/network/browser-local-ip-provider.ts +26 -0
- package/src/network/fixed-local-ip-provider.ts +9 -0
- package/src/network/local-ip-provider.ts +4 -0
- package/src/network/network-ratchet.spec.ts +17 -0
- package/src/network/network-ratchet.ts +209 -0
- package/src/network/remote-file-tracker/backup-result.ts +6 -0
- package/src/network/remote-file-tracker/file-transfer-result-type.ts +5 -0
- package/src/network/remote-file-tracker/file-transfer-result.ts +9 -0
- package/src/network/remote-file-tracker/remote-file-tracker-options.ts +6 -0
- package/src/network/remote-file-tracker/remote-file-tracker-push-options.ts +4 -0
- package/src/network/remote-file-tracker/remote-file-tracker.ts +117 -0
- package/src/network/remote-file-tracker/remote-file-tracking-provider.ts +19 -0
- package/src/network/remote-file-tracker/remote-status-data-and-content.ts +6 -0
- package/src/network/remote-file-tracker/remote-status-data.ts +7 -0
- package/src/network/restful-api-http-error.spec.ts +13 -0
- package/src/network/restful-api-http-error.ts +173 -0
- package/src/template/ratchet-template-renderer.ts +8 -0
- package/src/third-party/google/google-recaptcha-ratchet.spec.ts +27 -0
- package/src/third-party/google/google-recaptcha-ratchet.ts +36 -0
- package/src/third-party/twilio/twilio-ratchet.ts +92 -0
- package/src/third-party/twilio/twilio-verify-ratchet.ts +83 -0
- package/src/transform/built-in-transforms.ts +214 -0
- package/src/transform/transform-ratchet.spec.ts +134 -0
- package/src/transform/transform-ratchet.ts +88 -0
- package/src/transform/transform-rule.ts +7 -0
- package/src/tx/transaction-configuration.ts +8 -0
- package/src/tx/transaction-final-state.ts +7 -0
- package/src/tx/transaction-ratchet.spec.ts +150 -0
- package/src/tx/transaction-ratchet.ts +98 -0
- package/src/tx/transaction-result.ts +10 -0
- package/src/tx/transaction-step.ts +5 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ReadyToSendEmail } from './ready-to-send-email.js';
|
|
2
|
+
import { MailerConfig } from './mailer-config.js';
|
|
3
|
+
import { ResolvedReadyToSendEmail } from './resolved-ready-to-send-email.js';
|
|
4
|
+
import { EmailAttachment } from './email-attachment.js';
|
|
5
|
+
import { MailerLike } from './mailer-like.js';
|
|
6
|
+
import { RequireRatchet } from '../lang/require-ratchet.js';
|
|
7
|
+
import { ErrorRatchet } from '../lang/error-ratchet.js';
|
|
8
|
+
import { Logger } from '../logger/logger.js';
|
|
9
|
+
import { StringRatchet } from '../lang/string-ratchet.js';
|
|
10
|
+
import { Base64Ratchet } from '../lang/base64-ratchet.js';
|
|
11
|
+
import { SendEmailResult } from './send-email-result.js';
|
|
12
|
+
import { ArchiveEmailResult } from './archive-email-result.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generic Mail Sender
|
|
16
|
+
*
|
|
17
|
+
* Params:
|
|
18
|
+
* ses: AWS SES handler, properly configured
|
|
19
|
+
* defaultSendingAddress:
|
|
20
|
+
*/
|
|
21
|
+
export class Mailer<T, R> implements MailerLike<T, R> {
|
|
22
|
+
constructor(private config: MailerConfig<T, R>) {
|
|
23
|
+
RequireRatchet.notNullOrUndefined(config, 'config');
|
|
24
|
+
RequireRatchet.notNullOrUndefined(config.provider, 'config.provider');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async fillEmailBody(
|
|
28
|
+
rts: ReadyToSendEmail,
|
|
29
|
+
context: any,
|
|
30
|
+
htmlTemplateName: string,
|
|
31
|
+
txtTemplateName: string = null,
|
|
32
|
+
layoutName: string = null,
|
|
33
|
+
partialNames: string[] = null,
|
|
34
|
+
): Promise<ReadyToSendEmail> {
|
|
35
|
+
RequireRatchet.notNullOrUndefined(htmlTemplateName);
|
|
36
|
+
if (!this.config.templateRenderer) {
|
|
37
|
+
ErrorRatchet.throwFormattedErr('Cannot use fill body if template renderer not set');
|
|
38
|
+
}
|
|
39
|
+
rts.htmlMessage = await this.config.templateRenderer.renderTemplate(htmlTemplateName, context, layoutName, partialNames);
|
|
40
|
+
rts.txtMessage = txtTemplateName ? await this.config.templateRenderer.renderTemplate(txtTemplateName, context) : null;
|
|
41
|
+
return rts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async fillEmailBodyAndSend(
|
|
45
|
+
rts: ReadyToSendEmail,
|
|
46
|
+
context: any,
|
|
47
|
+
htmlTemplateName: string,
|
|
48
|
+
txtTemplateName: string = null,
|
|
49
|
+
layoutName: string = null,
|
|
50
|
+
partialNames: string[] = null,
|
|
51
|
+
): Promise<SendEmailResult<T, R>> {
|
|
52
|
+
const newVal: ReadyToSendEmail = await this.fillEmailBody(rts, context, htmlTemplateName, txtTemplateName, layoutName, partialNames);
|
|
53
|
+
const rval: SendEmailResult<T, R> = await this.sendEmail(newVal);
|
|
54
|
+
return rval;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public filterEmailsToValid(emails: string[]): string[] {
|
|
58
|
+
const rval: string[] = (emails || []).filter((e) => {
|
|
59
|
+
if (!this.config.allowedDestinationEmails || this.config.allowedDestinationEmails.length == 0) {
|
|
60
|
+
return true;
|
|
61
|
+
} else {
|
|
62
|
+
const match: RegExp = this.config.allowedDestinationEmails.find((s) => s.test(e));
|
|
63
|
+
return !!match;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return rval;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async archiveEmailIfConfigured(rts: ResolvedReadyToSendEmail, rawSendResult: T): Promise<ArchiveEmailResult<R>> {
|
|
70
|
+
let rval: ArchiveEmailResult<R> = null;
|
|
71
|
+
if (!!rts && !!this.config.provider.archiveEmail && !rts.doNotArchive) {
|
|
72
|
+
Logger.debug('Archiving outbound email to : %j', rts.destinationAddresses);
|
|
73
|
+
try {
|
|
74
|
+
const raw: R = await this.config.provider.archiveEmail(rts, rawSendResult);
|
|
75
|
+
rval = {
|
|
76
|
+
raw: raw,
|
|
77
|
+
error: null,
|
|
78
|
+
meta: {},
|
|
79
|
+
};
|
|
80
|
+
} catch (err) {
|
|
81
|
+
Logger.warn('Failed to archive email %j : %s', rts, err);
|
|
82
|
+
rval = {
|
|
83
|
+
raw: null,
|
|
84
|
+
error: ErrorRatchet.safeStringifyErr(err),
|
|
85
|
+
meta: {},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return rval;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public applyLimitsToBodySizesIfAnyInPlace(rts: ResolvedReadyToSendEmail): void {
|
|
93
|
+
if (this.config.maxMessageBodySizeInBytes) {
|
|
94
|
+
const txtSize: number = StringRatchet.trimToEmpty(rts.txtMessage).length;
|
|
95
|
+
const htmlSize: number = StringRatchet.trimToEmpty(rts.htmlMessage).length;
|
|
96
|
+
const totalSize: number = txtSize + htmlSize;
|
|
97
|
+
if (totalSize > this.config.maxMessageBodySizeInBytes) {
|
|
98
|
+
Logger.warn('Max message size is %d but size is %d - converting', this.config.maxMessageBodySizeInBytes, totalSize);
|
|
99
|
+
rts.attachments = rts.attachments || [];
|
|
100
|
+
if (StringRatchet.trimToNull(rts.txtMessage)) {
|
|
101
|
+
const txtAttach: EmailAttachment = {
|
|
102
|
+
filename: 'original-txt-body.txt',
|
|
103
|
+
contentType: 'text/plain',
|
|
104
|
+
base64Data: Base64Ratchet.generateBase64VersionOfString(rts.txtMessage),
|
|
105
|
+
};
|
|
106
|
+
rts.attachments.push(txtAttach);
|
|
107
|
+
}
|
|
108
|
+
if (StringRatchet.trimToNull(rts.htmlMessage)) {
|
|
109
|
+
const htmlAttach: EmailAttachment = {
|
|
110
|
+
filename: 'original-html-body.html',
|
|
111
|
+
contentType: 'text/html',
|
|
112
|
+
base64Data: Base64Ratchet.generateBase64VersionOfString(rts.htmlMessage),
|
|
113
|
+
};
|
|
114
|
+
rts.attachments.push(htmlAttach);
|
|
115
|
+
}
|
|
116
|
+
rts.htmlMessage = null;
|
|
117
|
+
rts.txtMessage = 'The message was too large and was converted to attachment(s). Please see attached files for content';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public applyLimitsToAttachmentSizesIfAnyInPlace(rts: ResolvedReadyToSendEmail): void {
|
|
123
|
+
if (this.config.maxAttachmentSizeInBase64Bytes) {
|
|
124
|
+
const filtered: EmailAttachment[] = [];
|
|
125
|
+
if (rts.attachments) {
|
|
126
|
+
rts.attachments.forEach((a) => {
|
|
127
|
+
if (a.base64Data && a.base64Data.length < this.config.maxAttachmentSizeInBase64Bytes) {
|
|
128
|
+
filtered.push(a);
|
|
129
|
+
} else {
|
|
130
|
+
Logger.warn('Removing too-large attachment : %s : %s : %d', a.filename, a.contentType, a.base64Data.length);
|
|
131
|
+
filtered.push({
|
|
132
|
+
filename: 'attachment-removed-notice-' + StringRatchet.createRandomHexString(4) + '.txt',
|
|
133
|
+
contentType: 'text/plain',
|
|
134
|
+
base64Data: Base64Ratchet.generateBase64VersionOfString(
|
|
135
|
+
'Attachment ' +
|
|
136
|
+
a.filename +
|
|
137
|
+
' of type ' +
|
|
138
|
+
a.contentType +
|
|
139
|
+
' was removed since it was ' +
|
|
140
|
+
a.base64Data.length +
|
|
141
|
+
' bytes but max allowed is ' +
|
|
142
|
+
this.config.maxAttachmentSizeInBase64Bytes,
|
|
143
|
+
),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
rts.attachments = filtered;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public async sendEmail(inRts: ReadyToSendEmail): Promise<SendEmailResult<T, R>> {
|
|
153
|
+
RequireRatchet.notNullOrUndefined(inRts, 'RTS must be defined');
|
|
154
|
+
RequireRatchet.notNullOrUndefined(inRts.destinationAddresses, 'Destination addresses must be defined');
|
|
155
|
+
let rval: SendEmailResult<T, R> = null;
|
|
156
|
+
|
|
157
|
+
const resolved: ResolvedReadyToSendEmail = await this.resolveReadyToSendEmail(inRts);
|
|
158
|
+
try {
|
|
159
|
+
const raw: T = await this.config.provider.sendEmail(resolved);
|
|
160
|
+
const archiveResult: ArchiveEmailResult<R> = await this.archiveEmailIfConfigured(resolved, raw);
|
|
161
|
+
rval = {
|
|
162
|
+
request: inRts,
|
|
163
|
+
resolved: resolved,
|
|
164
|
+
success: true,
|
|
165
|
+
rawResult: raw,
|
|
166
|
+
error: null,
|
|
167
|
+
meta: {},
|
|
168
|
+
archiveResults: archiveResult,
|
|
169
|
+
};
|
|
170
|
+
} catch (err) {
|
|
171
|
+
rval = {
|
|
172
|
+
request: inRts,
|
|
173
|
+
resolved: resolved,
|
|
174
|
+
success: false,
|
|
175
|
+
rawResult: null,
|
|
176
|
+
error: ErrorRatchet.safeStringifyErr(err),
|
|
177
|
+
meta: {},
|
|
178
|
+
archiveResults: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return rval;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public async resolveReadyToSendEmail(inRts: ReadyToSendEmail): Promise<ResolvedReadyToSendEmail> {
|
|
186
|
+
RequireRatchet.notNullOrUndefined(inRts, 'RTS must be defined');
|
|
187
|
+
RequireRatchet.notNullOrUndefined(inRts.destinationAddresses, 'Destination addresses must be defined');
|
|
188
|
+
|
|
189
|
+
let toAddresses: string[] = this.filterEmailsToValid(inRts.destinationAddresses);
|
|
190
|
+
const autoBcc: string[] = inRts.doNotAutoBcc ? [] : this.config.autoBccAddresses || [];
|
|
191
|
+
const bccAddresses: string[] = (inRts.bccAddresses || []).concat(autoBcc);
|
|
192
|
+
if (toAddresses.length === 0 && bccAddresses.length > 0) {
|
|
193
|
+
Logger.debug('Destination emails filtered to none but BCC defined, copying BCC');
|
|
194
|
+
toAddresses = bccAddresses;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const rts: ResolvedReadyToSendEmail = Object.assign({}, inRts);
|
|
198
|
+
rts.srcDestinationAddresses = inRts.destinationAddresses;
|
|
199
|
+
rts.srcBccAddresses = inRts.bccAddresses;
|
|
200
|
+
rts.destinationAddresses = toAddresses;
|
|
201
|
+
rts.bccAddresses = bccAddresses;
|
|
202
|
+
|
|
203
|
+
this.applyLimitsToBodySizesIfAnyInPlace(rts);
|
|
204
|
+
this.applyLimitsToAttachmentSizesIfAnyInPlace(rts);
|
|
205
|
+
|
|
206
|
+
if (rts.destinationAddresses.length === 0) {
|
|
207
|
+
Logger.info('After cleaning email lists, no destination addresses left - not sending email');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
rts.fromAddress = rts.fromAddress || this.config.defaultSendingAddress;
|
|
211
|
+
|
|
212
|
+
return rts;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @interface ReadyToSendEmail
|
|
3
|
+
*/
|
|
4
|
+
import { EmailAttachment } from './email-attachment.js';
|
|
5
|
+
|
|
6
|
+
export interface ReadyToSendEmail {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @type {Array<string>}
|
|
10
|
+
* @memberof ReadyToSendEmail
|
|
11
|
+
*/
|
|
12
|
+
destinationAddresses?: string[];
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @type {Array<string>}
|
|
16
|
+
* @memberof ReadyToSendEmail
|
|
17
|
+
*/
|
|
18
|
+
bccAddresses?: string[];
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @type {Array<EmailAttachment>}
|
|
22
|
+
* @memberof ReadyToSendEmail
|
|
23
|
+
*/
|
|
24
|
+
attachments?: EmailAttachment[];
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @type {string}
|
|
28
|
+
* @memberof ReadyToSendEmail
|
|
29
|
+
*/
|
|
30
|
+
fromAddress?: string;
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @type {string}
|
|
34
|
+
* @memberof ReadyToSendEmail
|
|
35
|
+
*/
|
|
36
|
+
txtMessage?: string;
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @type {string}
|
|
40
|
+
* @memberof ReadyToSendEmail
|
|
41
|
+
*/
|
|
42
|
+
htmlMessage?: string;
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @type {string}
|
|
46
|
+
* @memberof ReadyToSendEmail
|
|
47
|
+
*/
|
|
48
|
+
subject?: string;
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @type {string}
|
|
52
|
+
* @memberof ReadyToSendEmail
|
|
53
|
+
*/
|
|
54
|
+
fromName?: string;
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @type {boolean}
|
|
58
|
+
* @memberof ReadyToSendEmail
|
|
59
|
+
*/
|
|
60
|
+
doNotAutoBcc?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @type {boolean}
|
|
64
|
+
* @memberof ReadyToSendEmail
|
|
65
|
+
*/
|
|
66
|
+
doNotArchive?: boolean;
|
|
67
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @interface ResolvedReadyToSendEmail
|
|
3
|
+
*/
|
|
4
|
+
import { ReadyToSendEmail } from './ready-to-send-email.js';
|
|
5
|
+
|
|
6
|
+
export interface ResolvedReadyToSendEmail extends ReadyToSendEmail {
|
|
7
|
+
/**
|
|
8
|
+
* The list of destination addresses before any sending/filtering rules were applied
|
|
9
|
+
* @type {Array<string>}
|
|
10
|
+
* @memberof ReadyToSendEmail
|
|
11
|
+
*/
|
|
12
|
+
srcDestinationAddresses?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* The original list of bcc addresses before any auto-bcc were added
|
|
15
|
+
*/
|
|
16
|
+
srcBccAddresses?: string[];
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ReadyToSendEmail } from './ready-to-send-email.js';
|
|
2
|
+
import { ResolvedReadyToSendEmail } from './resolved-ready-to-send-email.js';
|
|
3
|
+
import { ArchiveEmailResult } from './archive-email-result.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Results of sending an email
|
|
7
|
+
*/
|
|
8
|
+
export interface SendEmailResult<T, R> {
|
|
9
|
+
request: ReadyToSendEmail;
|
|
10
|
+
resolved: ResolvedReadyToSendEmail;
|
|
11
|
+
success: boolean;
|
|
12
|
+
rawResult: T;
|
|
13
|
+
error: string;
|
|
14
|
+
meta: Record<string, any>;
|
|
15
|
+
archiveResults: ArchiveEmailResult<R>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ResolvedReadyToSendEmail } from './resolved-ready-to-send-email.js';
|
|
2
|
+
import { Logger } from '../logger/logger.js';
|
|
3
|
+
import { ErrorRatchet } from '../lang/error-ratchet.js';
|
|
4
|
+
import { MailSendingProvider } from './mail-sending-provider.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* An implementation of mail sending provider to simplify testing
|
|
8
|
+
*/
|
|
9
|
+
export class TestMailSendingProvider implements MailSendingProvider<string, string> {
|
|
10
|
+
constructor(public failEmails: string[] = []) {}
|
|
11
|
+
public async sendEmail(mail: ResolvedReadyToSendEmail): Promise<string> {
|
|
12
|
+
Logger.info('Called send email : %j', mail);
|
|
13
|
+
if (this.failEmails) {
|
|
14
|
+
this.failEmails.forEach((fe) => {
|
|
15
|
+
if (mail?.destinationAddresses?.includes(fe)) {
|
|
16
|
+
throw ErrorRatchet.fErr('Forced-fail email address: %s', fe);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return 'OK';
|
|
22
|
+
}
|
|
23
|
+
public async archiveEmail(mail: ResolvedReadyToSendEmail): Promise<string> {
|
|
24
|
+
Logger.info('Called archive email : %j', mail);
|
|
25
|
+
if (this.failEmails) {
|
|
26
|
+
this.failEmails.forEach((fe) => {
|
|
27
|
+
if (mail?.destinationAddresses?.includes(fe)) {
|
|
28
|
+
throw ErrorRatchet.fErr('Forced-fail email address: %s', fe);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return 'OK';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BrowserLocalIpProvider } from './browser-local-ip-provider.js';
|
|
2
|
+
import { NetworkRatchet } from './network-ratchet.js';
|
|
3
|
+
import { PromiseRatchet } from '../lang/promise-ratchet.js';
|
|
4
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
vi.mock('./network-ratchet');
|
|
7
|
+
|
|
8
|
+
describe('#browserLocalIpProvider', function () {
|
|
9
|
+
test('should pull a local ip and return it', async () => {
|
|
10
|
+
const mockStaticFn = vi.fn(() => Promise.resolve('192.168.1.1'));
|
|
11
|
+
|
|
12
|
+
NetworkRatchet.findLocalIp = mockStaticFn;
|
|
13
|
+
|
|
14
|
+
const up: BrowserLocalIpProvider = new BrowserLocalIpProvider();
|
|
15
|
+
|
|
16
|
+
// Just here to handle my old stupid not-understanding promises
|
|
17
|
+
while (!up.ready()) {
|
|
18
|
+
await PromiseRatchet.wait(250);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
expect(up.currentLocalIpAddress()).toEqual('192.168.1.1');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { LocalIpProvider } from './local-ip-provider.js';
|
|
2
|
+
import { NetworkRatchet } from './network-ratchet.js';
|
|
3
|
+
import { Logger } from '../logger/logger.js';
|
|
4
|
+
|
|
5
|
+
export class BrowserLocalIpProvider implements LocalIpProvider {
|
|
6
|
+
private currentIp = 'UNSET';
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
NetworkRatchet.findLocalIp(false)
|
|
10
|
+
.then((result) => {
|
|
11
|
+
Logger.info('Setting local IP to %s', result);
|
|
12
|
+
this.currentIp = result;
|
|
13
|
+
})
|
|
14
|
+
.catch((err) => {
|
|
15
|
+
Logger.warn('Unable to set current IP - leaving as UNSET : %s', err);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ready(): boolean {
|
|
20
|
+
return this.currentIp !== 'UNSET';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
currentLocalIpAddress(): string {
|
|
24
|
+
return this.currentIp;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NetworkRatchet } from './network-ratchet.js';
|
|
2
|
+
import { ParsedUrl } from '../lang/parsed-url.js';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#parseUrl', function () {
|
|
6
|
+
test('should parse the url and return correct values', function () {
|
|
7
|
+
const result: ParsedUrl = NetworkRatchet.parseUrl('https://example.com:3000/pathname/?search=test#hash');
|
|
8
|
+
|
|
9
|
+
expect(result.protocol).toEqual('https:');
|
|
10
|
+
expect(result.host).toEqual('example.com:3000');
|
|
11
|
+
expect(result.hostname).toEqual('example.com');
|
|
12
|
+
expect(result.port).toEqual('3000');
|
|
13
|
+
expect(result.pathname).toEqual('/pathname/');
|
|
14
|
+
expect(result.search).toEqual('?search=test');
|
|
15
|
+
expect(result.hash).toEqual('#hash');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Functions for simplifying some networking tasks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Logger } from '../logger/logger.js';
|
|
6
|
+
import { ParsedUrl } from '../lang/parsed-url.js';
|
|
7
|
+
|
|
8
|
+
export class NetworkRatchet {
|
|
9
|
+
private static LOCAL_IP: string = null;
|
|
10
|
+
|
|
11
|
+
// Prevent instantiation
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
13
|
+
private constructor() {}
|
|
14
|
+
|
|
15
|
+
public static findLocalIp(useCache = true): Promise<string> {
|
|
16
|
+
Logger.info('Attempting to find local IP (V 2)');
|
|
17
|
+
if (NetworkRatchet.LOCAL_IP && useCache) {
|
|
18
|
+
return Promise.resolve(NetworkRatchet.LOCAL_IP);
|
|
19
|
+
} else {
|
|
20
|
+
if (typeof window !== 'undefined') {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
22
|
+
return new Promise<string>(function (resolve, reject) {
|
|
23
|
+
try {
|
|
24
|
+
// NOTE: window.RTCPeerConnection is "not a constructor" in FF22/23
|
|
25
|
+
const RTCPeerConnection = window['RTCPeerConnection'] || window['webkitRTCPeerConnection'] || window['mozRTCPeerConnection'];
|
|
26
|
+
|
|
27
|
+
if (RTCPeerConnection) {
|
|
28
|
+
const rtc = new RTCPeerConnection({ iceServers: [] });
|
|
29
|
+
|
|
30
|
+
const addrs: any = Object.create(null);
|
|
31
|
+
addrs['0.0.0.0'] = false;
|
|
32
|
+
|
|
33
|
+
// FF [and now Chrome!] needs a channel/stream to proceed
|
|
34
|
+
rtc.createDataChannel('', { reliable: false });
|
|
35
|
+
/*
|
|
36
|
+
CAW - leaving this here for historical reasons, but obviously
|
|
37
|
+
if (1 ||... is always true, so moving it up
|
|
38
|
+
if (1 || window['mozRTCPeerConnection']) {
|
|
39
|
+
// FF [and now Chrome!] needs a channel/stream to proceed
|
|
40
|
+
rtc.createDataChannel('', { reliable: false });
|
|
41
|
+
}
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
rtc.onicecandidate = function (evt) {
|
|
45
|
+
// convert the candidate to SDP so we can run it through our general parser
|
|
46
|
+
// see https://twitter.com/lancestout/status/525796175425720320 for details
|
|
47
|
+
if (evt.candidate) {
|
|
48
|
+
NetworkRatchet.grepSDP('a=' + evt.candidate.candidate, addrs, resolve);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
rtc.createOffer(
|
|
53
|
+
function (offerDesc) {
|
|
54
|
+
NetworkRatchet.grepSDP(offerDesc.sdp, addrs, resolve);
|
|
55
|
+
rtc.setLocalDescription(offerDesc);
|
|
56
|
+
},
|
|
57
|
+
function (e) {
|
|
58
|
+
Logger.warn('Offer failed : %s', e);
|
|
59
|
+
resolve(NetworkRatchet.updateLocalIP('FIND_UNSUPPORTED'));
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
Logger.warn('IP Address find not supported on this device');
|
|
64
|
+
resolve(NetworkRatchet.updateLocalIP('FIND_UNSUPPORTED'));
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
Logger.warn('Error finding local ip address : %s', err);
|
|
68
|
+
resolve(NetworkRatchet.updateLocalIP('ERROR'));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
Logger.warn('Window not found, cannot calculate local ip');
|
|
73
|
+
return Promise.resolve(NetworkRatchet.updateLocalIP('NO_WINDOW'));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Break a url into a structure that is similar to what window.location returns
|
|
79
|
+
// @deprecated CAW 2024-08-09 : Here for backwards compatibility (Node did not used to have URL) - Just use new URL(x) now
|
|
80
|
+
public static parseUrl(href: string): ParsedUrl {
|
|
81
|
+
const url: URL = new URL(href);
|
|
82
|
+
const rval: ParsedUrl =
|
|
83
|
+
url &&
|
|
84
|
+
({
|
|
85
|
+
href: href,
|
|
86
|
+
protocol: url.protocol,
|
|
87
|
+
host: url.host,
|
|
88
|
+
hostname: url.hostname,
|
|
89
|
+
port: url.port,
|
|
90
|
+
pathname: url.pathname,
|
|
91
|
+
search: url.search,
|
|
92
|
+
hash: url.hash,
|
|
93
|
+
} as ParsedUrl);
|
|
94
|
+
return rval;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Just a helper function to make the build pattern here easier
|
|
98
|
+
private static updateLocalIP(newIp: string): string {
|
|
99
|
+
NetworkRatchet.LOCAL_IP = newIp;
|
|
100
|
+
return NetworkRatchet.LOCAL_IP;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static grepSDP(sdp, addrs, resolve): void {
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
105
|
+
const hosts: any = [];
|
|
106
|
+
sdp.split('\r\n').forEach(function (line) {
|
|
107
|
+
// c.f. http://tools.ietf.org/html/rfc4566#page-39
|
|
108
|
+
if (~line.indexOf('a=candidate')) {
|
|
109
|
+
// http://tools.ietf.org/html/rfc4566#section-5.13
|
|
110
|
+
const parts = line.split(' '), // http://tools.ietf.org/html/rfc5245#section-15.1
|
|
111
|
+
addr = parts[4],
|
|
112
|
+
type = parts[7];
|
|
113
|
+
if (type === 'host') {
|
|
114
|
+
NetworkRatchet.updateAddressList(addr, addrs, resolve);
|
|
115
|
+
}
|
|
116
|
+
} else if (~line.indexOf('c=')) {
|
|
117
|
+
// http://tools.ietf.org/html/rfc4566#section-5.7
|
|
118
|
+
const parts = line.split(' '),
|
|
119
|
+
addr = parts[2];
|
|
120
|
+
NetworkRatchet.updateAddressList(addr, addrs, resolve);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private static updateAddressList(newAddr, addrs, resolve): void {
|
|
126
|
+
if (newAddr in addrs) return;
|
|
127
|
+
else addrs[newAddr] = true;
|
|
128
|
+
const displayAddrs = Object.keys(addrs).filter(function (k) {
|
|
129
|
+
return addrs[k];
|
|
130
|
+
});
|
|
131
|
+
if (displayAddrs && displayAddrs.length == 1) {
|
|
132
|
+
resolve(NetworkRatchet.updateLocalIP(displayAddrs[0]));
|
|
133
|
+
} else {
|
|
134
|
+
const multi = displayAddrs.sort().join(',');
|
|
135
|
+
Logger.warn('Multiple addresses found, returning sorted join : %s', multi);
|
|
136
|
+
resolve(NetworkRatchet.updateLocalIP(multi));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
So, this code is actually nicer, but some browsers (e.g., the version of Chromium inside a 6.2.149.7 Brightsign)
|
|
142
|
+
don't support the promise version, so instead we have the ugly hack above. Leaving this in here for brighter
|
|
143
|
+
days in the future.
|
|
144
|
+
|
|
145
|
+
https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only
|
|
146
|
+
public static findLocalIp(useCache: boolean = true) : Promise<string>{
|
|
147
|
+
Logger.info("Attempting to find local IP (V 1)");
|
|
148
|
+
if (NetworkRatchet.LOCAL_IP && useCache) {
|
|
149
|
+
return Promise.resolve(NetworkRatchet.LOCAL_IP);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
if (typeof window !== "undefined") {
|
|
153
|
+
return new Promise<string>(function(resolve,reject){
|
|
154
|
+
//compatibility for firefox and chrome
|
|
155
|
+
// NOTE: window.RTCPeerConnection is "not a constructor" in FF22/23
|
|
156
|
+
var myPeerConnection = window['RTCPeerConnection'] || window['mozRTCPeerConnection'] || window['webkitRTCPeerConnection'];
|
|
157
|
+
var pc = new myPeerConnection({
|
|
158
|
+
iceServers: []
|
|
159
|
+
}),
|
|
160
|
+
noop = function() {},
|
|
161
|
+
localIPs = {},
|
|
162
|
+
ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g,
|
|
163
|
+
key;
|
|
164
|
+
|
|
165
|
+
//create a bogus data channel
|
|
166
|
+
pc.createDataChannel("");
|
|
167
|
+
|
|
168
|
+
// create offer and set local description
|
|
169
|
+
pc.createOffer({iceRestart:false}).then(function(sdp) {
|
|
170
|
+
sdp.sdp.split('\n').forEach(function(line) {
|
|
171
|
+
if (line.indexOf('candidate') < 0) return;
|
|
172
|
+
line.match(ipRegex).forEach((ip)=>{
|
|
173
|
+
if (!localIPs[ip]){
|
|
174
|
+
NetworkRatchet.LOCAL_IP = ip;
|
|
175
|
+
resolve(ip);
|
|
176
|
+
}
|
|
177
|
+
localIPs[ip] = true;
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
pc.setLocalDescription(sdp, noop, noop);
|
|
182
|
+
}).catch(function(reason) {
|
|
183
|
+
// An error occurred, so handle the failure to connect
|
|
184
|
+
Logger.warn("Failed to create peer connection offer : %s",reason);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
//listen for candidate events
|
|
188
|
+
pc.onicecandidate = function(ice) {
|
|
189
|
+
if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return;
|
|
190
|
+
ice.candidate.candidate.match(ipRegex).forEach((ip)=>{
|
|
191
|
+
if (!localIPs[ip]){
|
|
192
|
+
NetworkRatchet.LOCAL_IP = ip;
|
|
193
|
+
resolve(ip);
|
|
194
|
+
}
|
|
195
|
+
localIPs[ip] = true;
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
else
|
|
201
|
+
{
|
|
202
|
+
Logger.warn("Window not found, cannot calculate local ip");
|
|
203
|
+
NetworkRatchet.LOCAL_IP = "NO_WINDOW";
|
|
204
|
+
return Promise.resolve(NetworkRatchet.LOCAL_IP);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
*/
|
|
209
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FileTransferResultType } from './file-transfer-result-type.js';
|
|
2
|
+
import { BackupResult } from './backup-result.js';
|
|
3
|
+
|
|
4
|
+
export interface FileTransferResult {
|
|
5
|
+
type: FileTransferResultType;
|
|
6
|
+
error?: string;
|
|
7
|
+
bytesTransferred?: number;
|
|
8
|
+
backupResult: BackupResult;
|
|
9
|
+
}
|