@serve.zone/dcrouter 17.0.0 → 17.1.0

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 (139) hide show
  1. package/.smartconfig.json +5 -2
  2. package/deno.json +1 -1
  3. package/dist_serve/bundle.js +1316 -1151
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/classes.dcrouter.d.ts +3 -1
  6. package/dist_ts/classes.dcrouter.js +110 -49
  7. package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
  8. package/dist_ts/db/classes.cache.cleaner.js +7 -3
  9. package/dist_ts/db/classes.dcrouter-db.js +1 -2
  10. package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
  11. package/dist_ts/db/documents/classes.cached.email.js +36 -5
  12. package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
  13. package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
  14. package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
  15. package/dist_ts/dns/index.d.ts +1 -0
  16. package/dist_ts/dns/index.js +2 -1
  17. package/dist_ts/dns/manager.dns.d.ts +1 -0
  18. package/dist_ts/dns/manager.dns.js +49 -14
  19. package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
  20. package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
  21. package/dist_ts/dns/txt-record-presentation.js +131 -0
  22. package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
  23. package/dist_ts/email/classes.accepted-email-spool.js +174 -13
  24. package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
  25. package/dist_ts/email/classes.email-domain.manager.js +151 -8
  26. package/dist_ts/email/classes.email-settings.manager.js +2 -2
  27. package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
  28. package/dist_ts/email/classes.mail-dns-sync.js +508 -117
  29. package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +2 -2
  30. package/dist_ts/email/classes.smartmta-blob-storage-manager.js +4 -3
  31. package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
  32. package/dist_ts/email/classes.workapp-mail-manager.js +26 -2
  33. package/dist_ts/email/index.d.ts +2 -0
  34. package/dist_ts/email/index.js +3 -1
  35. package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
  36. package/dist_ts/email/mail-dns-txt-policy.js +466 -0
  37. package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
  38. package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
  39. package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
  40. package/dist_ts/opsserver/classes.opsserver.js +34 -1
  41. package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
  42. package/dist_ts/opsserver/handlers/config.handler.js +9 -5
  43. package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
  44. package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
  45. package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
  46. package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
  47. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
  48. package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
  49. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  50. package/dist_ts/opsserver/handlers/index.js +2 -1
  51. package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
  52. package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
  53. package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
  54. package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
  55. package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
  56. package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
  57. package/dist_ts/paths.d.ts +0 -11
  58. package/dist_ts/paths.js +1 -22
  59. package/dist_ts/plugins.d.ts +0 -34
  60. package/dist_ts/plugins.js +1 -62
  61. package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
  62. package/dist_ts_interfaces/requests/config.d.ts +1 -1
  63. package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
  64. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  65. package/dist_ts_interfaces/requests/index.js +2 -1
  66. package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
  67. package/dist_ts_interfaces/requests/realtime.js +11 -0
  68. package/dist_ts_migrations/index.d.ts +6 -0
  69. package/dist_ts_migrations/index.js +204 -9
  70. package/dist_ts_web/00_commitinfo_data.js +1 -1
  71. package/dist_ts_web/appstate/certificates.js +9 -2
  72. package/dist_ts_web/appstate/domains.js +16 -5
  73. package/dist_ts_web/appstate/email-domains.d.ts +3 -0
  74. package/dist_ts_web/appstate/email-domains.js +47 -14
  75. package/dist_ts_web/appstate/email-ops.d.ts +1 -0
  76. package/dist_ts_web/appstate/email-ops.js +20 -2
  77. package/dist_ts_web/appstate/realtime.d.ts +27 -0
  78. package/dist_ts_web/appstate/realtime.js +71 -0
  79. package/dist_ts_web/appstate/remoteingress.js +9 -3
  80. package/dist_ts_web/appstate/routes.js +16 -5
  81. package/dist_ts_web/appstate/runtime.js +135 -7
  82. package/dist_ts_web/appstate.d.ts +1 -0
  83. package/dist_ts_web/appstate.js +2 -1
  84. package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
  85. package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
  86. package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
  87. package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
  88. package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
  89. package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
  90. package/package.json +22 -19
  91. package/ts/00_commitinfo_data.ts +1 -1
  92. package/ts/classes.dcrouter.ts +121 -54
  93. package/ts/db/classes.cache.cleaner.ts +18 -3
  94. package/ts/db/classes.dcrouter-db.ts +0 -2
  95. package/ts/db/documents/classes.cached.email.ts +78 -4
  96. package/ts/db/documents/classes.dns-record.doc.ts +1 -0
  97. package/ts/dns/classes.dns-server-runtime.ts +9 -45
  98. package/ts/dns/index.ts +1 -0
  99. package/ts/dns/manager.dns.ts +50 -16
  100. package/ts/dns/providers/cloudflare.provider.ts +21 -3
  101. package/ts/dns/txt-record-presentation.ts +134 -0
  102. package/ts/email/classes.accepted-email-spool.ts +220 -14
  103. package/ts/email/classes.email-domain.manager.ts +201 -7
  104. package/ts/email/classes.email-settings.manager.ts +1 -1
  105. package/ts/email/classes.mail-dns-sync.ts +588 -122
  106. package/ts/email/classes.smartmta-blob-storage-manager.ts +3 -2
  107. package/ts/email/classes.workapp-mail-manager.ts +34 -1
  108. package/ts/email/index.ts +2 -0
  109. package/ts/email/mail-dns-txt-policy.ts +505 -0
  110. package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
  111. package/ts/opsserver/classes.opsserver.ts +42 -0
  112. package/ts/opsserver/handlers/certificate.handler.ts +24 -3
  113. package/ts/opsserver/handlers/config.handler.ts +9 -5
  114. package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
  115. package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
  116. package/ts/opsserver/handlers/domain.handler.ts +36 -2
  117. package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
  118. package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
  119. package/ts/opsserver/handlers/index.ts +1 -0
  120. package/ts/opsserver/handlers/logs.handler.ts +2 -2
  121. package/ts/opsserver/handlers/realtime.handler.ts +222 -0
  122. package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
  123. package/ts/opsserver/handlers/route-management.handler.ts +46 -4
  124. package/ts/paths.ts +0 -25
  125. package/ts/plugins.ts +0 -68
  126. package/ts_web/00_commitinfo_data.ts +1 -1
  127. package/ts_web/appstate/certificates.ts +6 -1
  128. package/ts_web/appstate/domains.ts +11 -4
  129. package/ts_web/appstate/email-domains.ts +68 -14
  130. package/ts_web/appstate/email-ops.ts +20 -1
  131. package/ts_web/appstate/realtime.ts +109 -0
  132. package/ts_web/appstate/remoteingress.ts +6 -2
  133. package/ts_web/appstate/routes.ts +11 -4
  134. package/ts_web/appstate/runtime.ts +176 -7
  135. package/ts_web/appstate.ts +1 -0
  136. package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
  137. package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
  138. package/ts_web/elements/email/ops-view-emails.ts +23 -16
  139. package/ts_web/elements/overview/ops-view-config.ts +4 -4
@@ -0,0 +1,340 @@
1
+ import type * as interfaces from '../../../dist_ts_interfaces/index.js';
2
+
3
+ export interface IEmailDomainCreationProgress {
4
+ percentage: number;
5
+ indeterminate: boolean;
6
+ showPercentage: boolean;
7
+ statusText: string;
8
+ terminalLines: string[];
9
+ }
10
+
11
+ export interface IEmailDomainCreationProgressInput {
12
+ domain?: interfaces.data.IEmailDomain;
13
+ operation?: interfaces.requests.IEmailDomainActionResult | null;
14
+ requestCompleted: boolean;
15
+ }
16
+
17
+ const settledIntentStatuses = new Set<interfaces.data.TEmailDnsIntentStatus>([
18
+ 'satisfied',
19
+ 'valid',
20
+ 'conflict',
21
+ 'failed',
22
+ ]);
23
+
24
+ const formatOperationError = (
25
+ errorArg: interfaces.data.IEmailDomainOperationError | interfaces.requests.IEmailDomainActionError,
26
+ ): string => {
27
+ const location = errorArg.recordKey ? `${errorArg.recordKey}: ` : '';
28
+ return `${location}${errorArg.message} [${errorArg.code}]`;
29
+ };
30
+
31
+ const appendDnsStatusLines = (
32
+ linesArg: string[],
33
+ dnsStatusArg: interfaces.data.IEmailDomainDnsStatus,
34
+ ): void => {
35
+ for (const [label, status] of [
36
+ ['MX', dnsStatusArg.mx],
37
+ ['SPF', dnsStatusArg.spf],
38
+ ['DKIM', dnsStatusArg.dkim],
39
+ ['DMARC', dnsStatusArg.dmarc],
40
+ ] as const) {
41
+ if (status !== 'unchecked') {
42
+ linesArg.push(`${label}: ${status}`);
43
+ }
44
+ }
45
+ };
46
+
47
+ export const getEmailDomainCreationProgress = (
48
+ inputArg: IEmailDomainCreationProgressInput,
49
+ ): IEmailDomainCreationProgress => {
50
+ const { domain, operation, requestCompleted } = inputArg;
51
+
52
+ if (!domain) {
53
+ if (!requestCompleted) {
54
+ return {
55
+ percentage: 0,
56
+ indeterminate: true,
57
+ showPercentage: false,
58
+ statusText: 'Generating DKIM material and creating the domain record...',
59
+ terminalLines: ['Submitting email domain configuration'],
60
+ };
61
+ }
62
+
63
+ const lines = ['Email domain creation request completed without a persisted domain'];
64
+ for (const error of operation?.errors || []) {
65
+ lines.push(formatOperationError(error));
66
+ }
67
+ if (operation?.message && !lines.includes(operation.message)) {
68
+ lines.push(operation.message);
69
+ }
70
+ return {
71
+ percentage: 100,
72
+ indeterminate: false,
73
+ showPercentage: false,
74
+ statusText: operation?.message || 'Email domain creation failed.',
75
+ terminalLines: lines,
76
+ };
77
+ }
78
+
79
+ const reconciliation = domain.reconciliation;
80
+ const lines = [`Domain record persisted: ${domain.domain}`];
81
+ if (!reconciliation || reconciliation.intents.length === 0) {
82
+ if (!requestCompleted) {
83
+ lines.push('Preparing the managed DNS plan');
84
+ return {
85
+ percentage: 0,
86
+ indeterminate: true,
87
+ showPercentage: false,
88
+ statusText: 'Preparing the managed DNS plan...',
89
+ terminalLines: lines,
90
+ };
91
+ }
92
+
93
+ lines.push('No DNS reconciliation plan is available');
94
+ const errors = reconciliation?.errors.length
95
+ ? reconciliation.errors
96
+ : operation?.errors || [];
97
+ for (const error of errors) {
98
+ lines.push(formatOperationError(error));
99
+ }
100
+ const retryAt = reconciliation?.retryAt || operation?.retryAt;
101
+ if (retryAt) {
102
+ lines.push(`Retry scheduled for ${retryAt}`);
103
+ }
104
+ const statusText =
105
+ reconciliation?.lifecycleStatus === 'failed'
106
+ ? operation?.message || `DNS setup failed for ${domain.domain}.`
107
+ : retryAt
108
+ ? 'DNS reconciliation is pending and will retry automatically.'
109
+ : operation?.message || 'The domain was created without a DNS reconciliation plan.';
110
+ return {
111
+ percentage: 100,
112
+ indeterminate: false,
113
+ showPercentage: false,
114
+ statusText,
115
+ terminalLines: lines,
116
+ };
117
+ }
118
+
119
+ const settledCount = reconciliation.intents.filter((intent) =>
120
+ settledIntentStatuses.has(intent.status)
121
+ ).length;
122
+ lines.push(`DNS plan prepared: ${reconciliation.intents.length} managed records`);
123
+ lines.push(
124
+ `DNS reconciliation: ${settledCount} settled, ${reconciliation.intents.length - settledCount} pending`,
125
+ );
126
+ appendDnsStatusLines(lines, domain.dnsStatus);
127
+
128
+ if (reconciliation.lifecycleStatus === 'active') {
129
+ lines.push('Email domain is active');
130
+ return {
131
+ percentage: 100,
132
+ indeterminate: false,
133
+ showPercentage: true,
134
+ statusText: `${domain.domain} is active.`,
135
+ terminalLines: lines,
136
+ };
137
+ }
138
+
139
+ if (!requestCompleted) {
140
+ return {
141
+ percentage: 0,
142
+ indeterminate: true,
143
+ showPercentage: false,
144
+ statusText: 'Reconciling and validating managed DNS records...',
145
+ terminalLines: lines,
146
+ };
147
+ }
148
+
149
+ const errors = reconciliation.errors.length > 0
150
+ ? reconciliation.errors
151
+ : operation?.errors || [];
152
+ for (const error of errors) {
153
+ lines.push(formatOperationError(error));
154
+ }
155
+ const retryAt = reconciliation.retryAt || operation?.retryAt;
156
+ if (retryAt) {
157
+ lines.push(`Retry scheduled for ${retryAt}`);
158
+ }
159
+
160
+ if (reconciliation.lifecycleStatus === 'failed') {
161
+ return {
162
+ percentage: 100,
163
+ indeterminate: false,
164
+ showPercentage: false,
165
+ statusText: operation?.message || `DNS setup failed for ${domain.domain}.`,
166
+ terminalLines: lines,
167
+ };
168
+ }
169
+
170
+ return {
171
+ percentage: 100,
172
+ indeterminate: false,
173
+ showPercentage: false,
174
+ statusText: retryAt
175
+ ? `DNS reconciliation is pending and will retry automatically.`
176
+ : `DNS reconciliation is pending for ${domain.domain}.`,
177
+ terminalLines: lines,
178
+ };
179
+ };
180
+
181
+ export const findNewEmailDomain = (
182
+ domainsArg: interfaces.data.IEmailDomain[],
183
+ expectedDomainArg: string,
184
+ existingIdsArg: ReadonlySet<string>,
185
+ ): interfaces.data.IEmailDomain | undefined => {
186
+ const expectedDomain = expectedDomainArg.trim().toLowerCase();
187
+ const candidates = domainsArg.filter(
188
+ (domain) => domain.domain.toLowerCase() === expectedDomain && !existingIdsArg.has(domain.id),
189
+ );
190
+ if (candidates.length > 1) {
191
+ throw new Error(`Multiple new email domains appeared for ${expectedDomain}`);
192
+ }
193
+ return candidates[0];
194
+ };
195
+
196
+ const waitForPollInterval = async (delayMsArg: number, signalArg: AbortSignal): Promise<void> => {
197
+ if (signalArg.aborted) return;
198
+ await new Promise<void>((resolve) => {
199
+ const finish = () => {
200
+ signalArg.removeEventListener('abort', handleAbort);
201
+ resolve();
202
+ };
203
+ const handleAbort = () => {
204
+ clearTimeout(timeoutId);
205
+ finish();
206
+ };
207
+ const timeoutId = setTimeout(finish, delayMsArg);
208
+ signalArg.addEventListener('abort', handleAbort, { once: true });
209
+ });
210
+ };
211
+
212
+ export interface IObserveNewEmailDomainOptions {
213
+ expectedDomain: string;
214
+ existingIds: ReadonlySet<string>;
215
+ signal: AbortSignal;
216
+ listDomains: () => Promise<interfaces.data.IEmailDomain[]>;
217
+ getDomain: (idArg: string) => Promise<interfaces.data.IEmailDomain | null>;
218
+ onDomain: (domainArg: interfaces.data.IEmailDomain) => void;
219
+ subscribeToDomains?: (
220
+ listenerArg: (domainsArg: interfaces.data.IEmailDomain[]) => void,
221
+ ) => () => void;
222
+ pollIntervalMs?: number;
223
+ timeoutMs?: number;
224
+ }
225
+
226
+ export const observeNewEmailDomain = async (
227
+ optionsArg: IObserveNewEmailDomainOptions,
228
+ ): Promise<interfaces.data.IEmailDomain | undefined> => {
229
+ const startedAt = Date.now();
230
+ const pollIntervalMs = optionsArg.pollIntervalMs ?? (
231
+ optionsArg.subscribeToDomains ? 15_000 : 600
232
+ );
233
+ const timeoutMs = optionsArg.timeoutMs ?? 120_000;
234
+ let observedId: string | undefined;
235
+ let latestDomain: interfaces.data.IEmailDomain | undefined;
236
+ let pushedDomains: interfaces.data.IEmailDomain[] | undefined;
237
+ let wakeObserver: (() => void) | undefined;
238
+
239
+ const unsubscribe = optionsArg.subscribeToDomains?.((domainsArg) => {
240
+ pushedDomains = domainsArg;
241
+ wakeObserver?.();
242
+ });
243
+
244
+ try {
245
+ while (!optionsArg.signal.aborted && Date.now() - startedAt < timeoutMs) {
246
+ try {
247
+ let domain: interfaces.data.IEmailDomain | null | undefined;
248
+ if (!observedId) {
249
+ const domainSnapshot = pushedDomains || await optionsArg.listDomains();
250
+ pushedDomains = undefined;
251
+ const foundDomain = findNewEmailDomain(
252
+ domainSnapshot,
253
+ optionsArg.expectedDomain,
254
+ optionsArg.existingIds,
255
+ );
256
+ observedId = foundDomain?.id;
257
+ domain = foundDomain;
258
+ } else if (pushedDomains) {
259
+ domain = pushedDomains.find((candidate) => candidate.id === observedId);
260
+ pushedDomains = undefined;
261
+ } else {
262
+ domain = await optionsArg.getDomain(observedId);
263
+ }
264
+
265
+ if (optionsArg.signal.aborted) break;
266
+ if (domain) {
267
+ if (domain !== latestDomain) {
268
+ latestDomain = domain;
269
+ optionsArg.onDomain(domain);
270
+ }
271
+ const lifecycleStatus = domain.reconciliation?.lifecycleStatus;
272
+ if (lifecycleStatus === 'active' || lifecycleStatus === 'failed') {
273
+ return domain;
274
+ }
275
+ }
276
+ } catch (error: unknown) {
277
+ if (error instanceof Error && error.message.startsWith('Multiple new email domains')) {
278
+ throw error;
279
+ }
280
+ // The realtime subscription or bounded fallback read will retry.
281
+ }
282
+
283
+ const updatePromise = new Promise<void>((resolve) => {
284
+ wakeObserver = resolve;
285
+ });
286
+ if (pushedDomains) {
287
+ wakeObserver = undefined;
288
+ continue;
289
+ }
290
+ await Promise.race([
291
+ updatePromise,
292
+ waitForPollInterval(pollIntervalMs, optionsArg.signal),
293
+ ]);
294
+ wakeObserver = undefined;
295
+ }
296
+ } finally {
297
+ unsubscribe?.();
298
+ wakeObserver?.();
299
+ }
300
+
301
+ return latestDomain;
302
+ };
303
+
304
+ export interface IAbortableDeadlineOptions {
305
+ signal?: AbortSignal;
306
+ timeoutMs: number;
307
+ timeoutMessage: string;
308
+ }
309
+
310
+ export const withAbortableDeadline = async <T>(
311
+ promiseArg: Promise<T>,
312
+ optionsArg: IAbortableDeadlineOptions,
313
+ ): Promise<T> => {
314
+ return new Promise<T>((resolve, reject) => {
315
+ let settled = false;
316
+ const finish = (callbackArg: () => void) => {
317
+ if (settled) return;
318
+ settled = true;
319
+ clearTimeout(timeoutId);
320
+ optionsArg.signal?.removeEventListener('abort', handleAbort);
321
+ callbackArg();
322
+ };
323
+ const handleAbort = () => {
324
+ finish(() => reject(new Error('Email domain creation progress was interrupted')));
325
+ };
326
+ const timeoutId = setTimeout(() => {
327
+ finish(() => reject(new Error(optionsArg.timeoutMessage)));
328
+ }, optionsArg.timeoutMs);
329
+
330
+ if (optionsArg.signal?.aborted) {
331
+ handleAbort();
332
+ return;
333
+ }
334
+ optionsArg.signal?.addEventListener('abort', handleAbort, { once: true });
335
+ promiseArg.then(
336
+ (value) => finish(() => resolve(value)),
337
+ (error) => finish(() => reject(error)),
338
+ );
339
+ });
340
+ };