@bitblit/ratchet-common 6.0.146-alpha → 6.0.148-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,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,9 @@
1
+ import { LocalIpProvider } from './local-ip-provider.js';
2
+
3
+ export class FixedLocalIpProvider implements LocalIpProvider {
4
+ constructor(private fixed: string) {}
5
+
6
+ currentLocalIpAddress(): string {
7
+ return this.fixed;
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ /** Classes implementing this interface can provide the current local ip address (as a string) * */
2
+ export interface LocalIpProvider {
3
+ currentLocalIpAddress(): string;
4
+ }
@@ -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,6 @@
1
+ export enum BackupResult {
2
+ Success = 'Success',
3
+ Error = 'Error',
4
+ NotSupported = 'NotSupported',
5
+ NotRequested = 'NotRequested',
6
+ }
@@ -0,0 +1,5 @@
1
+ export enum FileTransferResultType {
2
+ Updated = 'Updated', // File was sent
3
+ Skipped = 'Skipped', // Update was skipped for some reason (see logs)
4
+ Error = 'Error',
5
+ }
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ import { RemoteFileTrackingProvider } from './remote-file-tracking-provider.js';
2
+
3
+ export interface RemoteFileTrackerOptions<KeyType> {
4
+ key: KeyType;
5
+ provider: RemoteFileTrackingProvider<KeyType>;
6
+ }