@bitblit/ratchet-common 6.0.146-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.
Files changed (140) hide show
  1. package/package.json +2 -1
  2. package/src/2d/line-2d.ts +6 -0
  3. package/src/2d/matrix-factory.ts +94 -0
  4. package/src/2d/plane-2d-type.ts +6 -0
  5. package/src/2d/plane-2d.ts +7 -0
  6. package/src/2d/point-2d.ts +4 -0
  7. package/src/2d/poly-line-2d.ts +5 -0
  8. package/src/2d/ratchet-2d.spec.ts +205 -0
  9. package/src/2d/ratchet-2d.ts +350 -0
  10. package/src/2d/transformation-matrix.ts +19 -0
  11. package/src/build/build-information.ts +8 -0
  12. package/src/build/ratchet-common-info.ts +19 -0
  13. package/src/histogram/histogram-entry.ts +4 -0
  14. package/src/histogram/histogram.spec.ts +25 -0
  15. package/src/histogram/histogram.ts +61 -0
  16. package/src/jwt/common-jwt-token.ts +17 -0
  17. package/src/jwt/expired-jwt-handling.ts +5 -0
  18. package/src/jwt/jwt-decode-only-ratchet.ts +26 -0
  19. package/src/jwt/jwt-payload-expiration-ratchet.ts +45 -0
  20. package/src/jwt/jwt-token-base.ts +14 -0
  21. package/src/lang/array-ratchet.spec.ts +79 -0
  22. package/src/lang/array-ratchet.ts +141 -0
  23. package/src/lang/base64-ratchet.spec.ts +48 -0
  24. package/src/lang/base64-ratchet.ts +247 -0
  25. package/src/lang/boolean-ratchet.spec.ts +95 -0
  26. package/src/lang/boolean-ratchet.ts +52 -0
  27. package/src/lang/composite-last-success-provider.spec.ts +31 -0
  28. package/src/lang/composite-last-success-provider.ts +30 -0
  29. package/src/lang/currency-ratchet.ts +29 -0
  30. package/src/lang/date-ratchet.spec.ts +27 -0
  31. package/src/lang/date-ratchet.ts +42 -0
  32. package/src/lang/duration-ratchet.spec.ts +47 -0
  33. package/src/lang/duration-ratchet.ts +77 -0
  34. package/src/lang/enum-ratchet.spec.ts +45 -0
  35. package/src/lang/enum-ratchet.ts +41 -0
  36. package/src/lang/error-handling-approach.ts +6 -0
  37. package/src/lang/error-ratchet.spec.ts +25 -0
  38. package/src/lang/error-ratchet.ts +70 -0
  39. package/src/lang/esm-ratchet.ts +81 -0
  40. package/src/lang/expiring-object.spec.ts +56 -0
  41. package/src/lang/expiring-object.ts +84 -0
  42. package/src/lang/geolocation-ratchet.spec.ts +177 -0
  43. package/src/lang/geolocation-ratchet.ts +341 -0
  44. package/src/lang/global-ratchet.spec.ts +17 -0
  45. package/src/lang/global-ratchet.ts +105 -0
  46. package/src/lang/key-value.ts +8 -0
  47. package/src/lang/last-success-provider.ts +4 -0
  48. package/src/lang/map-ratchet.spec.ts +113 -0
  49. package/src/lang/map-ratchet.ts +220 -0
  50. package/src/lang/no.spec.ts +9 -0
  51. package/src/lang/no.ts +7 -0
  52. package/src/lang/number-ratchet.spec.ts +154 -0
  53. package/src/lang/number-ratchet.ts +253 -0
  54. package/src/lang/parsed-url.ts +10 -0
  55. package/src/lang/promise-ratchet.spec.ts +104 -0
  56. package/src/lang/promise-ratchet.ts +196 -0
  57. package/src/lang/range.ts +4 -0
  58. package/src/lang/require-ratchet.spec.ts +85 -0
  59. package/src/lang/require-ratchet.ts +68 -0
  60. package/src/lang/simple-arg-ratchet.spec.ts +13 -0
  61. package/src/lang/simple-arg-ratchet.ts +47 -0
  62. package/src/lang/simple-encryption-ratchet.ts +88 -0
  63. package/src/lang/sort-ratchet.spec.ts +58 -0
  64. package/src/lang/sort-ratchet.ts +50 -0
  65. package/src/lang/stop-watch.spec.ts +53 -0
  66. package/src/lang/stop-watch.ts +202 -0
  67. package/src/lang/string-ratchet.spec.ts +226 -0
  68. package/src/lang/string-ratchet.ts +676 -0
  69. package/src/lang/time-zone-ratchet.spec.ts +51 -0
  70. package/src/lang/time-zone-ratchet.ts +148 -0
  71. package/src/lang/timeout-token.spec.ts +12 -0
  72. package/src/lang/timeout-token.ts +21 -0
  73. package/src/lang/uint-8-array-ratchet.spec.ts +22 -0
  74. package/src/lang/uint-8-array-ratchet.ts +48 -0
  75. package/src/lang/web-stream-ratchet.spec.ts +12 -0
  76. package/src/lang/web-stream-ratchet.ts +96 -0
  77. package/src/logger/classic-single-line-log-message-formatter.ts +19 -0
  78. package/src/logger/log-message-builder.ts +60 -0
  79. package/src/logger/log-message-format-type.ts +11 -0
  80. package/src/logger/log-message-formatter.ts +6 -0
  81. package/src/logger/log-message-processor.ts +6 -0
  82. package/src/logger/log-message.ts +9 -0
  83. package/src/logger/log-snapshot.ts +6 -0
  84. package/src/logger/logger-instance.ts +269 -0
  85. package/src/logger/logger-level-name.ts +11 -0
  86. package/src/logger/logger-meta.ts +7 -0
  87. package/src/logger/logger-options.ts +14 -0
  88. package/src/logger/logger-output-function.ts +10 -0
  89. package/src/logger/logger-ring-buffer.ts +89 -0
  90. package/src/logger/logger-util.spec.ts +11 -0
  91. package/src/logger/logger-util.ts +68 -0
  92. package/src/logger/logger.spec.ts +177 -0
  93. package/src/logger/logger.ts +213 -0
  94. package/src/logger/none-log-message-formatter.ts +10 -0
  95. package/src/logger/single-line-no-level-log-message-formatter.ts +18 -0
  96. package/src/logger/structured-json-log-message-formatter.ts +25 -0
  97. package/src/mail/archive-email-result.ts +8 -0
  98. package/src/mail/email-attachment.ts +23 -0
  99. package/src/mail/mail-sending-provider.ts +21 -0
  100. package/src/mail/mailer-config.ts +30 -0
  101. package/src/mail/mailer-like.ts +38 -0
  102. package/src/mail/mailer-util.ts +65 -0
  103. package/src/mail/mailer.spec.ts +120 -0
  104. package/src/mail/mailer.ts +214 -0
  105. package/src/mail/ready-to-send-email.ts +67 -0
  106. package/src/mail/resolved-ready-to-send-email.ts +17 -0
  107. package/src/mail/send-email-result.ts +16 -0
  108. package/src/mail/test-mail-sending-provider.ts +35 -0
  109. package/src/network/browser-local-ip-provider.spec.ts +23 -0
  110. package/src/network/browser-local-ip-provider.ts +26 -0
  111. package/src/network/fixed-local-ip-provider.ts +9 -0
  112. package/src/network/local-ip-provider.ts +4 -0
  113. package/src/network/network-ratchet.spec.ts +17 -0
  114. package/src/network/network-ratchet.ts +209 -0
  115. package/src/network/remote-file-tracker/backup-result.ts +6 -0
  116. package/src/network/remote-file-tracker/file-transfer-result-type.ts +5 -0
  117. package/src/network/remote-file-tracker/file-transfer-result.ts +9 -0
  118. package/src/network/remote-file-tracker/remote-file-tracker-options.ts +6 -0
  119. package/src/network/remote-file-tracker/remote-file-tracker-push-options.ts +4 -0
  120. package/src/network/remote-file-tracker/remote-file-tracker.ts +117 -0
  121. package/src/network/remote-file-tracker/remote-file-tracking-provider.ts +19 -0
  122. package/src/network/remote-file-tracker/remote-status-data-and-content.ts +6 -0
  123. package/src/network/remote-file-tracker/remote-status-data.ts +7 -0
  124. package/src/network/restful-api-http-error.spec.ts +13 -0
  125. package/src/network/restful-api-http-error.ts +173 -0
  126. package/src/template/ratchet-template-renderer.ts +8 -0
  127. package/src/third-party/google/google-recaptcha-ratchet.spec.ts +27 -0
  128. package/src/third-party/google/google-recaptcha-ratchet.ts +36 -0
  129. package/src/third-party/twilio/twilio-ratchet.ts +92 -0
  130. package/src/third-party/twilio/twilio-verify-ratchet.ts +83 -0
  131. package/src/transform/built-in-transforms.ts +214 -0
  132. package/src/transform/transform-ratchet.spec.ts +134 -0
  133. package/src/transform/transform-ratchet.ts +88 -0
  134. package/src/transform/transform-rule.ts +7 -0
  135. package/src/tx/transaction-configuration.ts +8 -0
  136. package/src/tx/transaction-final-state.ts +7 -0
  137. package/src/tx/transaction-ratchet.spec.ts +150 -0
  138. package/src/tx/transaction-ratchet.ts +98 -0
  139. package/src/tx/transaction-result.ts +10 -0
  140. package/src/tx/transaction-step.ts +5 -0
@@ -0,0 +1,213 @@
1
+ import { LogMessage } from './log-message.js';
2
+ import { LogSnapshot } from './log-snapshot.js';
3
+ import { LoggerOptions } from './logger-options.js';
4
+ import { LoggerInstance } from './logger-instance.js';
5
+ import { LogMessageFormatType } from './log-message-format-type.js';
6
+ import { LoggerLevelName } from './logger-level-name.js';
7
+ import { LoggerRingBuffer } from './logger-ring-buffer.js';
8
+ import { LogMessageBuilder } from './log-message-builder.js';
9
+ import { LoggerOutputFunction } from './logger-output-function.js';
10
+ import { GlobalRatchet } from '../lang/global-ratchet.js';
11
+
12
+ /**
13
+ * Class to simplify logging across both browsers and node (especially lambda)
14
+ *
15
+ * Important note : This class is here to make logging SIMPLE - mainly it is focused on simplicity,
16
+ * and single point of enable/disable logging. Output goes to console, since thats the only thing
17
+ * that is present in both the browser and node. If you need to do something more complicated (like
18
+ * logging to files, multiple transports, etc) you really should just use something heavier like
19
+ * Winston directly and skip this class entirely
20
+ */
21
+
22
+ export class Logger {
23
+ private static LOGGER_INSTANCE_MAP_GLOBAL_KEY = 'RATCHET_GLOBAL_LOGGER_MAP_V01';
24
+ //private static LOGGER_INSTANCES: Map<string, LoggerInstance> = new Map<string, LoggerInstance>();
25
+ private static DEFAULT_OPTIONS: LoggerOptions = {
26
+ initialLevel: LoggerLevelName.info,
27
+ formatType: LogMessageFormatType.ClassicSingleLine,
28
+ trace: null,
29
+ globalVars: {},
30
+ outputFunction: LoggerOutputFunction.Console,
31
+ ringBufferSize: 0,
32
+ preProcessors: [],
33
+ };
34
+
35
+ public static applyDefaultsToOptions(input?: Partial<LoggerOptions>): LoggerOptions {
36
+ const rval: LoggerOptions = input || {};
37
+ rval.initialLevel = rval.initialLevel ?? Logger.DEFAULT_OPTIONS.initialLevel;
38
+ rval.formatType = rval.formatType ?? Logger.DEFAULT_OPTIONS.formatType;
39
+ rval.trace = rval.trace ?? Logger.DEFAULT_OPTIONS.trace;
40
+ rval.globalVars = rval.globalVars ?? Logger.DEFAULT_OPTIONS.globalVars;
41
+ rval.outputFunction = rval.outputFunction ?? Logger.DEFAULT_OPTIONS.outputFunction;
42
+ rval.ringBufferSize = rval.ringBufferSize ?? Logger.DEFAULT_OPTIONS.ringBufferSize;
43
+
44
+ return rval;
45
+ }
46
+
47
+ private static loggerInstances(): Map<string, LoggerInstance> {
48
+ GlobalRatchet.fetchGlobalVarsRecord(false); // Will throw an exception if global cant setup
49
+ let rval: Map<string, LoggerInstance> = GlobalRatchet.fetchGlobalVar(Logger.LOGGER_INSTANCE_MAP_GLOBAL_KEY);
50
+ if (!rval) {
51
+ rval = new Map<string, LoggerInstance>();
52
+ GlobalRatchet.setGlobalVar(Logger.LOGGER_INSTANCE_MAP_GLOBAL_KEY, rval);
53
+ }
54
+ return rval;
55
+ }
56
+
57
+ public static changeDefaultOptions(input: LoggerOptions, retroactive?: boolean): void {
58
+ if (!input || !input.initialLevel || !input.formatType) {
59
+ throw new Error('Default options must be non-null, and provide initial level and format type');
60
+ }
61
+ Logger.DEFAULT_OPTIONS = Object.assign({}, input);
62
+
63
+ if (retroactive) {
64
+ Array.from(Logger.loggerInstances().values()).forEach((li) => (li.options = input));
65
+ }
66
+ }
67
+
68
+ public static getLogger(loggerName = 'default', inOptions?: LoggerOptions): LoggerInstance {
69
+ let inst: LoggerInstance = Logger.loggerInstances().get(loggerName);
70
+ if (!inst) {
71
+ const options: LoggerOptions = Logger.applyDefaultsToOptions(inOptions);
72
+ inst = new LoggerInstance(loggerName, options);
73
+ Logger.loggerInstances().set(loggerName, inst);
74
+ }
75
+ return inst;
76
+ }
77
+
78
+ // Classic passthru's direct to the default logger
79
+
80
+ public static recordMessageBuilder(msgBuild: LogMessageBuilder): string {
81
+ return Logger.getLogger().recordMessageBuilder(msgBuild);
82
+ }
83
+
84
+ public static levelIsEnabled(level: LoggerLevelName): boolean {
85
+ return Logger.getLogger().levelIsEnabled(level);
86
+ }
87
+
88
+ public static recordMessage(msg: LogMessage): string {
89
+ return Logger.getLogger().recordMessage(msg);
90
+ }
91
+
92
+ public static formatMessages(msgs: LogMessage[]): string[] {
93
+ return Logger.getLogger().formatMessages(msgs);
94
+ }
95
+
96
+ public static updateTracePrefix(newValue: string): void {
97
+ return Logger.getLogger().updateTracePrefix(newValue);
98
+ }
99
+
100
+ public static changeRingBufferSize(newValue: number): void {
101
+ return Logger.getLogger().changeRingBufferSize(newValue);
102
+ }
103
+
104
+ public static getRingBuffer(): LoggerRingBuffer {
105
+ return Logger.getLogger().ringBuffer;
106
+ }
107
+
108
+ public static dumpConfigurationIntoLog(): void {
109
+ return Logger.getLogger().dumpConfigurationIntoLog();
110
+ }
111
+
112
+ public static dumpOptionsIntoLog(): void {
113
+ return Logger.getLogger().dumpOptionsIntoLog();
114
+ }
115
+
116
+ public static getLevel(): LoggerLevelName {
117
+ return Logger.getLogger().level;
118
+ }
119
+
120
+ public static setLevel(newLevel: LoggerLevelName): void {
121
+ Logger.getLogger().level = newLevel;
122
+ }
123
+
124
+ // Defaulting to silly because it'll typically be post-filtered
125
+ public static ringBufferOnlyMode(ringBufferSize: number = 1000, initialLevel: LoggerLevelName = LoggerLevelName.silly): void {
126
+ const newOptions: LoggerOptions = Logger.applyDefaultsToOptions({
127
+ ringBufferSize: ringBufferSize,
128
+ outputFunction: LoggerOutputFunction.Disabled,
129
+ formatType: LogMessageFormatType.SingleLineNoLevel,
130
+ initialLevel: initialLevel,
131
+ });
132
+ Logger.changeDefaultOptions(newOptions, true);
133
+ }
134
+
135
+ public static getOptions(): LoggerOptions {
136
+ return Logger.getLogger().options;
137
+ }
138
+
139
+ public static getMessages(inStartFrom: number = null, clear = false, reverseSort = false): LogMessage[] {
140
+ const buf: LoggerRingBuffer = Logger.getLogger().ringBuffer;
141
+ return buf ? buf.getMessages(inStartFrom, clear, reverseSort) : null;
142
+ }
143
+
144
+ public static consoleLogPassThru(level: LoggerLevelName, ...input: any[]): void {
145
+ return Logger.getLogger().consoleLogPassThru(level, ...input);
146
+ }
147
+
148
+ public static error(format: string, ...input: any[]): string {
149
+ return Logger.getLogger().error(format, ...input);
150
+ }
151
+
152
+ public static errorP(...input: any[]): void {
153
+ return Logger.getLogger().errorP(...input);
154
+ }
155
+
156
+ public static warn(format: string, ...input: any[]): string {
157
+ return Logger.getLogger().warn(format, ...input);
158
+ }
159
+
160
+ public static warnP(...input: any[]): void {
161
+ return Logger.getLogger().warnP(...input);
162
+ }
163
+
164
+ public static info(format: string, ...input: any[]): string {
165
+ return Logger.getLogger().info(format, ...input);
166
+ }
167
+
168
+ public static infoP(...input: any[]): void {
169
+ return Logger.getLogger().infoP(...input);
170
+ }
171
+
172
+ public static verbose(format: string, ...input: any[]): string {
173
+ return Logger.getLogger().verbose(format, ...input);
174
+ }
175
+
176
+ public static verboseP(...input: any[]): void {
177
+ return Logger.getLogger().verboseP(...input);
178
+ }
179
+
180
+ public static debug(format: string, ...input: any[]): string {
181
+ return Logger.getLogger().debug(format, ...input);
182
+ }
183
+
184
+ public static debugP(...input: any[]): void {
185
+ return Logger.getLogger().debugP(...input);
186
+ }
187
+
188
+ public static silly(format: string, ...input: any[]): string {
189
+ return Logger.getLogger().silly(format, ...input);
190
+ }
191
+
192
+ public static sillyP(...input: any[]): void {
193
+ return Logger.getLogger().sillyP(...input);
194
+ }
195
+
196
+ public static takeSnapshot(): LogSnapshot {
197
+ const buf: LoggerRingBuffer = Logger.getLogger().ringBuffer;
198
+ return buf ? buf.takeSnapshot() : null;
199
+ }
200
+
201
+ public static logByLevel(level: LoggerLevelName, format: string, ...input: any[]): void {
202
+ return Logger.getLogger().logByLevel(level, format, ...input);
203
+ }
204
+
205
+ public static importMessages(msgs: LogMessage[], prefixIn = '', addTimestamp = true): void {
206
+ return Logger.getLogger().importMessages(msgs, prefixIn, addTimestamp);
207
+ }
208
+
209
+ public static getLastLogMessage(): LogMessage {
210
+ const buf: LoggerRingBuffer = Logger.getLogger().ringBuffer;
211
+ return buf ? buf.getLastLogMessage() : null;
212
+ }
213
+ }
@@ -0,0 +1,10 @@
1
+ import { LogMessage } from './log-message.js';
2
+ import { LogMessageFormatter } from './log-message-formatter.js';
3
+ import { LoggerMeta } from './logger-meta.js';
4
+
5
+ // No-op, useful for testing and turning off all logging
6
+ export class NoneLogMessageFormatter implements LogMessageFormatter {
7
+ public formatMessage(_msg: LogMessage, _meta: LoggerMeta): string {
8
+ return null;
9
+ }
10
+ }
@@ -0,0 +1,18 @@
1
+ import { LogMessage } from './log-message.js';
2
+ import { LogMessageFormatter } from './log-message-formatter.js';
3
+ import { LoggerMeta } from './logger-meta.js';
4
+ import { StringRatchet } from '../lang/string-ratchet.js';
5
+
6
+ // Used when the output will be put into a table with the other fields separate
7
+ export class SingleLineNoLevelLogMessageFormatter implements LogMessageFormatter {
8
+ public formatMessage(msg: LogMessage, meta: LoggerMeta): string {
9
+ let tmp: string = null;
10
+
11
+ if (msg) {
12
+ tmp = '';
13
+ tmp += meta?.options?.trace ? meta.options.trace + ' ' : '';
14
+ tmp += StringRatchet.format(msg?.messageSource ?? '', ...(msg.subsVars || []));
15
+ }
16
+ return tmp;
17
+ }
18
+ }
@@ -0,0 +1,25 @@
1
+ import { LogMessage } from './log-message.js';
2
+ import { LogMessageFormatter } from './log-message-formatter.js';
3
+ import { LoggerMeta } from './logger-meta.js';
4
+ import { StringRatchet } from '../lang/string-ratchet.js';
5
+
6
+ export class StructuredJsonLogMessageFormatter implements LogMessageFormatter {
7
+ public formatMessage(msg: LogMessage, meta: LoggerMeta): string {
8
+ let rval: string = null;
9
+ if (msg) {
10
+ const tmp: Record<any, any> = Object.assign({}, meta?.options?.globalVars || {}, msg?.params || {});
11
+ tmp['msg'] = StringRatchet.format(msg?.messageSource, ...(msg.subsVars || []));
12
+ tmp['utcDateTime'] = new Date(msg.timestamp).toISOString();
13
+ tmp['logLevel'] = msg.lvl;
14
+ if (meta?.options?.trace) {
15
+ tmp['trace'] = meta.options.trace;
16
+ }
17
+ tmp['logName'] = meta.loggerInstanceName;
18
+ tmp['logId'] = meta.loggerInstanceId;
19
+
20
+ rval = JSON.stringify(tmp) + '\n'; // You must have this newline at the end if using stdout instead of console.log
21
+ // see https://stackoverflow.com/questions/4976466/difference-between-process-stdout-write-and-console-log-in-node-js
22
+ }
23
+ return rval;
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Results of sending an email
3
+ */
4
+ export interface ArchiveEmailResult<R> {
5
+ raw: R;
6
+ error: string;
7
+ meta: Record<string, any>;
8
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * tbd
3
+ * @interface EmailAttachment
4
+ */
5
+ export interface EmailAttachment {
6
+ /**
7
+ * @type {string}
8
+ * @memberof EmailAttachment
9
+ */
10
+ filename?: string;
11
+ /**
12
+ *
13
+ * @type {string}
14
+ * @memberof EmailAttachment
15
+ */
16
+ contentType?: string;
17
+ /**
18
+ *
19
+ * @type {string}
20
+ * @memberof EmailAttachment
21
+ */
22
+ base64Data?: string;
23
+ }
@@ -0,0 +1,21 @@
1
+ import { ResolvedReadyToSendEmail } from './resolved-ready-to-send-email.js';
2
+
3
+ /**
4
+ * Generic Mail Sender.
5
+ *
6
+ * If the provider needs the raw text version, it can call the function in MailerUtil to convert it.
7
+ * If it provides an archive function, it will be called
8
+ *
9
+ * for AWS.
10
+ *
11
+ * Params:
12
+ * ses: AWS SES handler, properly configured
13
+ * defaultSendingAddress:
14
+ *
15
+ * T is the object returned by the Mail sender when you send an email
16
+ * R is the object returned by the Mail archiver when you archive an email
17
+ */
18
+ export interface MailSendingProvider<T, R> {
19
+ sendEmail(mail: ResolvedReadyToSendEmail): Promise<T>;
20
+ archiveEmail?(mail: ResolvedReadyToSendEmail, rawSendResult: T): Promise<R>;
21
+ }
@@ -0,0 +1,30 @@
1
+ import { MailSendingProvider } from './mail-sending-provider.js';
2
+ import { RatchetTemplateRenderer } from '../template/ratchet-template-renderer.js';
3
+
4
+ /**
5
+ * Configuration options for generic mailer
6
+ *
7
+ * T is the object returned by the Mail sender when you send an email
8
+ * R is the object returned by the Mail archiver when you archive an email
9
+ */
10
+ export interface MailerConfig<T, R> {
11
+ // The thing that actually sends the email
12
+ provider: MailSendingProvider<T, R>;
13
+ // If no sending email is set, and this is, then it is used
14
+ defaultSendingAddress?: string;
15
+ // These addresses get added as BCC to any outbound email
16
+ // If filters end up removing all destination addresses, this is copied into the destination so they still get sent
17
+ // This behavior is for dev/demo style configurations where you still want to see the email even though the email
18
+ // itself isn't sent
19
+ autoBccAddresses?: string[];
20
+ // If set, the mailer object can delegate template filling to this template renderer.
21
+ templateRenderer?: RatchetTemplateRenderer;
22
+ // If set, any outbound email address will be checked against this list and removed if it does not match
23
+ // Typically used for testing dev/demo configs
24
+ allowedDestinationEmails?: RegExp[];
25
+ // If set, if the txt or html bodies are larger than this they will be auto-converted to attachments
26
+ // For SES, this should be 10485760 or less as of 2021-01-27
27
+ maxMessageBodySizeInBytes?: number;
28
+ // If set, any attachments larger than this are auto-dropped
29
+ maxAttachmentSizeInBase64Bytes?: number;
30
+ }
@@ -0,0 +1,38 @@
1
+ import { ReadyToSendEmail } from './ready-to-send-email.js';
2
+ import { ResolvedReadyToSendEmail } from './resolved-ready-to-send-email.js';
3
+ import { SendEmailResult } from './send-email-result.js';
4
+
5
+ /**
6
+ * Generic Mail Sender for AWS.
7
+ *
8
+ * Params:
9
+ * ses: AWS SES handler, properly configured
10
+ * defaultSendingAddress:
11
+ */
12
+ export interface MailerLike<T, R> {
13
+ fillEmailBody(
14
+ rts: ReadyToSendEmail,
15
+ context: any,
16
+ htmlTemplateName: string,
17
+ txtTemplateName?: string,
18
+ layoutName?: string,
19
+ partialNames?: string[],
20
+ ): Promise<ReadyToSendEmail>;
21
+
22
+ fillEmailBodyAndSend(
23
+ rts: ReadyToSendEmail,
24
+ context: any,
25
+ htmlTemplateName: string,
26
+ txtTemplateName?: string,
27
+ layoutName?: string,
28
+ partialNames?: string[],
29
+ ): Promise<SendEmailResult<T, R>>;
30
+
31
+ filterEmailsToValid(emails: string[]): string[];
32
+
33
+ applyLimitsToBodySizesIfAnyInPlace(rts: ResolvedReadyToSendEmail): void;
34
+
35
+ applyLimitsToAttachmentSizesIfAnyInPlace(rts: ResolvedReadyToSendEmail): void;
36
+
37
+ sendEmail(inRts: ReadyToSendEmail): Promise<SendEmailResult<T, R>>;
38
+ }
@@ -0,0 +1,65 @@
1
+ import { ResolvedReadyToSendEmail } from './resolved-ready-to-send-email.js';
2
+ import { RequireRatchet } from '../lang/require-ratchet.js';
3
+ import { StringRatchet } from '../lang/string-ratchet.js';
4
+
5
+ /**
6
+ * Helper functions for the mailer, most notably the one that converts it to raw
7
+ * SMTP
8
+ */
9
+ export class MailerUtil {
10
+ public static readonly EMAIL: RegExp = new RegExp('.+@.+\\.[a-z]+');
11
+
12
+ // Prevent instantiation
13
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
14
+ private constructor() {}
15
+
16
+ public static convertResolvedReadyToSendEmailToRaw(rts: ResolvedReadyToSendEmail): string {
17
+ RequireRatchet.notNullOrUndefined(rts, 'RTS must be defined');
18
+ RequireRatchet.notNullOrUndefined(rts.destinationAddresses, 'Destination addresses must be defined');
19
+
20
+ const toLine: string = 'To: ' + rts.destinationAddresses.join(', ') + '\n';
21
+ const bccLine: string = !!rts.bccAddresses && rts.bccAddresses.length > 0 ? 'Bcc: ' + rts.bccAddresses.join(', ') + '\n' : '';
22
+
23
+ const from: string = rts.fromAddress;
24
+ const boundary = 'NextPart';
25
+ const altBoundary = 'AltPart';
26
+ let rawMail: string = 'From: ' + from + '\n';
27
+ rawMail += toLine;
28
+ rawMail += bccLine;
29
+ rawMail += 'Subject: ' + rts.subject + '\n';
30
+ rawMail += 'MIME-Version: 1.0\n';
31
+ rawMail += 'Content-Type: multipart/mixed; boundary="' + boundary + '"\n';
32
+
33
+ rawMail += '\n\n--' + boundary + '\n';
34
+ rawMail += 'Content-Type: multipart/alternative; boundary="' + altBoundary + '"\n';
35
+ if (StringRatchet.trimToNull(rts.htmlMessage)) {
36
+ rawMail += '\n\n--' + altBoundary + '\n';
37
+ rawMail += 'Content-Type: text/html; charset="UTF-8"\n\n';
38
+ rawMail += rts.htmlMessage;
39
+ }
40
+ if (StringRatchet.trimToNull(rts.txtMessage)) {
41
+ rawMail += '\n\n--' + altBoundary + '\n';
42
+ rawMail += 'Content-Type: text/plain\n\n';
43
+ rawMail += rts.txtMessage;
44
+ }
45
+
46
+ rawMail += '\n\n--' + altBoundary + '--\n';
47
+
48
+ if (rts.attachments) {
49
+ rts.attachments.forEach((a) => {
50
+ rawMail += '\n\n--' + boundary + '\n';
51
+ rawMail += 'Content-Type: ' + a.contentType + '; name="' + a.filename + '"\n';
52
+ rawMail += 'Content-Transfer-Encoding: base64\n';
53
+ rawMail += 'Content-Disposition: attachment\n\n';
54
+ rawMail += a.base64Data.replace(/([^\0]{76})/g, '$1\n') + '\n\n';
55
+ });
56
+ }
57
+ rawMail += '\n\n--' + boundary + '--\n';
58
+
59
+ return rawMail;
60
+ }
61
+
62
+ public static validEmail(email: string): boolean {
63
+ return email !== null && MailerUtil.EMAIL.test(email);
64
+ }
65
+ }
@@ -0,0 +1,120 @@
1
+ import { ReadyToSendEmail } from './ready-to-send-email.js';
2
+ import { Mailer } from './mailer.js';
3
+ import { EmailAttachment } from './email-attachment.js';
4
+ import { MailerConfig } from './mailer-config.js';
5
+ import { StringRatchet } from '../lang/string-ratchet.js';
6
+ import { Base64Ratchet } from '../lang/base64-ratchet.js';
7
+ import { TestMailSendingProvider } from './test-mail-sending-provider.js';
8
+ import { SendEmailResult } from './send-email-result.js';
9
+ import { describe, expect, test } from 'vitest';
10
+
11
+ const smallImageBase64 =
12
+ 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
13
+
14
+ describe('#mailer', function () {
15
+ const testProvider: TestMailSendingProvider = new TestMailSendingProvider();
16
+ //beforeEach(() => {});
17
+
18
+ test('should send email', async () => {
19
+ const config: MailerConfig<string, string> = {
20
+ provider: testProvider,
21
+ defaultSendingAddress: 'test1@test.com',
22
+ autoBccAddresses: [], //['test2@test.com','test2@test.com'],
23
+ };
24
+ const svc: Mailer<string, string> = new Mailer<string, string>(config);
25
+
26
+ const attach1: EmailAttachment = {
27
+ filename: 'test.txt',
28
+ contentType: 'text/plain',
29
+ base64Data: Base64Ratchet.generateBase64VersionOfString('This is a test2'),
30
+ };
31
+
32
+ const attach2: EmailAttachment = {
33
+ filename: 'a2.png',
34
+ contentType: 'image/png',
35
+ base64Data: smallImageBase64,
36
+ };
37
+
38
+ const rts: ReadyToSendEmail = {
39
+ txtMessage: 'test txt',
40
+ htmlMessage: '<h1>Test html</h1><p>Test paragraph</p>',
41
+ subject: 'Test subject',
42
+ fromAddress: 'test@test.com',
43
+ destinationAddresses: ['testout@test.com'],
44
+ attachments: [attach1, attach2],
45
+ };
46
+
47
+ const result: SendEmailResult<string, string> = await svc.sendEmail(rts);
48
+
49
+ expect(result).toBeTruthy();
50
+ });
51
+
52
+ test('should allow for unicode in email subject', async () => {
53
+ const config: MailerConfig<string, string> = {
54
+ provider: testProvider,
55
+ defaultSendingAddress: 'jflint@adomni.com',
56
+ autoBccAddresses: [],
57
+ };
58
+ const svc: Mailer<string, string> = new Mailer<string, string>(config);
59
+ const rts: ReadyToSendEmail = {
60
+ txtMessage: 'test txt',
61
+ htmlMessage: '<h1>Test html</h1><p>Test paragraph</p>',
62
+ subject: "Rappel: Votre panneau d'affichage Shout est diffusé aujourd'hui!", // <==== We are testing these unicode characters
63
+ fromAddress: 'jflint@adomni.com',
64
+ destinationAddresses: ['jflint@adomni.com'],
65
+ };
66
+
67
+ const result: SendEmailResult<string, string> = await svc.sendEmail(rts);
68
+ expect(result).toBeTruthy();
69
+ });
70
+
71
+ test('should filter outbound', async () => {
72
+ const config: MailerConfig<string, string> = {
73
+ provider: testProvider,
74
+ allowedDestinationEmails: [/.*test\.com/, /.*.test2\.com/],
75
+ };
76
+ const svc: Mailer<string, string> = new Mailer<string, string>(config);
77
+
78
+ const out1: string[] = ['a@test.com', 'b@fail.com'];
79
+ const res1: string[] = svc.filterEmailsToValid(out1);
80
+ expect(res1).toBeTruthy();
81
+ expect(res1.length).toEqual(1);
82
+
83
+ const out2: string[] = ['a@fail.com', 'b@fail.com'];
84
+ const res2: string[] = svc.filterEmailsToValid(out2);
85
+ expect(res2).toBeTruthy();
86
+ expect(res2.length).toEqual(0);
87
+ });
88
+
89
+ test('should fix a huge text/html body', async () => {
90
+ const config: MailerConfig<string, string> = {
91
+ provider: testProvider,
92
+ defaultSendingAddress: 'test@test.com',
93
+ autoBccAddresses: [], //['test2@test.com','test2@test.com'],
94
+ maxMessageBodySizeInBytes: 500,
95
+ maxAttachmentSizeInBase64Bytes: 1000,
96
+ };
97
+ const svc: Mailer<string, string> = new Mailer<string, string>(config);
98
+
99
+ const bigBody: string = StringRatchet.createRandomHexString(300);
100
+
101
+ const bigAttach: EmailAttachment = {
102
+ filename: 'test.txt',
103
+ contentType: 'text/plain',
104
+ base64Data: Base64Ratchet.generateBase64VersionOfString(StringRatchet.createRandomHexString(2000)),
105
+ };
106
+
107
+ const rts: ReadyToSendEmail = {
108
+ txtMessage: bigBody,
109
+ htmlMessage: bigBody,
110
+ subject: 'Test big message',
111
+ fromAddress: 'test@test.com',
112
+ destinationAddresses: ['test@test.com'],
113
+ attachments: [bigAttach],
114
+ };
115
+
116
+ const result: SendEmailResult<string, string> = await svc.sendEmail(rts);
117
+
118
+ expect(result).toBeTruthy();
119
+ });
120
+ });