@serve.zone/dcrouter 16.5.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.
- package/.smartconfig.json +5 -2
- package/deno.json +1 -1
- package/dist_serve/bundle.js +1316 -1151
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +6 -1
- package/dist_ts/classes.dcrouter.js +119 -41
- package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
- package/dist_ts/db/classes.cache.cleaner.js +7 -3
- package/dist_ts/db/classes.dcrouter-db.js +1 -2
- package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
- package/dist_ts/db/documents/classes.cached.email.js +36 -5
- package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
- package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +8 -0
- package/dist_ts/db/documents/classes.smartmta-storage.doc.js +91 -0
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
- package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
- package/dist_ts/dns/index.d.ts +1 -0
- package/dist_ts/dns/index.js +2 -1
- package/dist_ts/dns/manager.dns.d.ts +1 -0
- package/dist_ts/dns/manager.dns.js +49 -14
- package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
- package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
- package/dist_ts/dns/txt-record-presentation.js +131 -0
- package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
- package/dist_ts/email/classes.accepted-email-spool.js +174 -13
- package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
- package/dist_ts/email/classes.email-domain.manager.js +151 -8
- package/dist_ts/email/classes.email-settings.manager.js +2 -1
- package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
- package/dist_ts/email/classes.mail-dns-sync.js +508 -117
- package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +27 -0
- package/dist_ts/email/classes.smartmta-blob-storage-manager.js +110 -0
- package/dist_ts/email/classes.smartmta-storage-manager.d.ts +6 -8
- package/dist_ts/email/classes.smartmta-storage-manager.js +63 -86
- package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
- package/dist_ts/email/classes.workapp-mail-manager.js +31 -4
- package/dist_ts/email/index.d.ts +4 -0
- package/dist_ts/email/index.js +5 -1
- package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
- package/dist_ts/email/mail-dns-txt-policy.js +466 -0
- package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
- package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
- package/dist_ts/email/smartmta-storage-policy.d.ts +9 -0
- package/dist_ts/email/smartmta-storage-policy.js +58 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
- package/dist_ts/opsserver/classes.opsserver.js +34 -1
- package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
- package/dist_ts/opsserver/handlers/config.handler.js +9 -5
- package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
- package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
- package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
- package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
- package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
- package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
- package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
- package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
- package/dist_ts/opsserver/handlers/stats.handler.js +3 -5
- package/dist_ts/paths.d.ts +0 -11
- package/dist_ts/paths.js +1 -22
- package/dist_ts/plugins.d.ts +2 -35
- package/dist_ts/plugins.js +3 -63
- package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
- package/dist_ts_interfaces/requests/config.d.ts +1 -1
- package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
- package/dist_ts_interfaces/requests/realtime.js +11 -0
- package/dist_ts_migrations/index.d.ts +8 -0
- package/dist_ts_migrations/index.js +489 -12
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/certificates.js +9 -2
- package/dist_ts_web/appstate/domains.js +16 -5
- package/dist_ts_web/appstate/email-domains.d.ts +3 -0
- package/dist_ts_web/appstate/email-domains.js +47 -14
- package/dist_ts_web/appstate/email-ops.d.ts +1 -0
- package/dist_ts_web/appstate/email-ops.js +20 -2
- package/dist_ts_web/appstate/realtime.d.ts +27 -0
- package/dist_ts_web/appstate/realtime.js +71 -0
- package/dist_ts_web/appstate/remoteingress.js +9 -3
- package/dist_ts_web/appstate/routes.js +16 -5
- package/dist_ts_web/appstate/runtime.js +135 -7
- package/dist_ts_web/appstate.d.ts +1 -0
- package/dist_ts_web/appstate.js +2 -1
- package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
- package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
- package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
- package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
- package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
- package/package.json +23 -19
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +136 -44
- package/ts/db/classes.cache.cleaner.ts +18 -3
- package/ts/db/classes.dcrouter-db.ts +0 -2
- package/ts/db/documents/classes.cached.email.ts +78 -4
- package/ts/db/documents/classes.dns-record.doc.ts +1 -0
- package/ts/db/documents/classes.smartmta-storage.doc.ts +25 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/dns/classes.dns-server-runtime.ts +9 -45
- package/ts/dns/index.ts +1 -0
- package/ts/dns/manager.dns.ts +50 -16
- package/ts/dns/providers/cloudflare.provider.ts +21 -3
- package/ts/dns/txt-record-presentation.ts +134 -0
- package/ts/email/classes.accepted-email-spool.ts +220 -14
- package/ts/email/classes.email-domain.manager.ts +201 -7
- package/ts/email/classes.email-settings.manager.ts +1 -0
- package/ts/email/classes.mail-dns-sync.ts +588 -122
- package/ts/email/classes.smartmta-blob-storage-manager.ts +139 -0
- package/ts/email/classes.smartmta-storage-manager.ts +86 -83
- package/ts/email/classes.workapp-mail-manager.ts +47 -4
- package/ts/email/index.ts +4 -0
- package/ts/email/mail-dns-txt-policy.ts +505 -0
- package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
- package/ts/email/smartmta-storage-policy.ts +73 -0
- package/ts/opsserver/classes.opsserver.ts +42 -0
- package/ts/opsserver/handlers/certificate.handler.ts +24 -3
- package/ts/opsserver/handlers/config.handler.ts +9 -5
- package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
- package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
- package/ts/opsserver/handlers/domain.handler.ts +36 -2
- package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
- package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/logs.handler.ts +2 -2
- package/ts/opsserver/handlers/realtime.handler.ts +222 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
- package/ts/opsserver/handlers/route-management.handler.ts +46 -4
- package/ts/opsserver/handlers/stats.handler.ts +2 -4
- package/ts/paths.ts +0 -25
- package/ts/plugins.ts +2 -69
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/certificates.ts +6 -1
- package/ts_web/appstate/domains.ts +11 -4
- package/ts_web/appstate/email-domains.ts +68 -14
- package/ts_web/appstate/email-ops.ts +20 -1
- package/ts_web/appstate/realtime.ts +109 -0
- package/ts_web/appstate/remoteingress.ts +6 -2
- package/ts_web/appstate/routes.ts +11 -4
- package/ts_web/appstate/runtime.ts +176 -7
- package/ts_web/appstate.ts +1 -0
- package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
- package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
- package/ts_web/elements/email/ops-view-emails.ts +23 -16
- package/ts_web/elements/overview/ops-view-config.ts +4 -4
|
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import * as interfaces from '../../ts_interfaces/index.js';
|
|
3
3
|
import { appState } from './shared.js';
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
|
+
import { captureRealtimeTicket, isRealtimeTicketCurrent } from './realtime.js';
|
|
5
6
|
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// Domains State (DNS providers + domains + records)
|
|
@@ -37,6 +38,7 @@ export const fetchDomainsAndProvidersAction = domainsStatePart.createAction(
|
|
|
37
38
|
const context = getActionContext();
|
|
38
39
|
const currentState = statePartArg.getState()!;
|
|
39
40
|
if (!context.identity) return currentState;
|
|
41
|
+
const ticket = captureRealtimeTicket('dns');
|
|
40
42
|
|
|
41
43
|
try {
|
|
42
44
|
const providersRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -50,9 +52,10 @@ export const fetchDomainsAndProvidersAction = domainsStatePart.createAction(
|
|
|
50
52
|
providersRequest.fire({ identity: context.identity }),
|
|
51
53
|
domainsRequest.fire({ identity: context.identity }),
|
|
52
54
|
]);
|
|
55
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
53
56
|
|
|
54
57
|
return {
|
|
55
|
-
...
|
|
58
|
+
...statePartArg.getState()!,
|
|
56
59
|
providers: providersResponse.providers,
|
|
57
60
|
domains: domainsResponse.domains,
|
|
58
61
|
isLoading: false,
|
|
@@ -60,8 +63,9 @@ export const fetchDomainsAndProvidersAction = domainsStatePart.createAction(
|
|
|
60
63
|
lastUpdated: Date.now(),
|
|
61
64
|
};
|
|
62
65
|
} catch (error: unknown) {
|
|
66
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
63
67
|
return {
|
|
64
|
-
...
|
|
68
|
+
...statePartArg.getState()!,
|
|
65
69
|
isLoading: false,
|
|
66
70
|
error: error instanceof Error ? error.message : 'Failed to fetch domains/providers',
|
|
67
71
|
};
|
|
@@ -74,6 +78,7 @@ export const fetchDnsRecordsForDomainAction = domainsStatePart.createAction<{ do
|
|
|
74
78
|
const context = getActionContext();
|
|
75
79
|
const currentState = statePartArg.getState()!;
|
|
76
80
|
if (!context.identity) return currentState;
|
|
81
|
+
const ticket = captureRealtimeTicket('dns');
|
|
77
82
|
|
|
78
83
|
try {
|
|
79
84
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -83,15 +88,17 @@ export const fetchDnsRecordsForDomainAction = domainsStatePart.createAction<{ do
|
|
|
83
88
|
identity: context.identity,
|
|
84
89
|
domainId: dataArg.domainId,
|
|
85
90
|
});
|
|
91
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
86
92
|
return {
|
|
87
|
-
...
|
|
93
|
+
...statePartArg.getState()!,
|
|
88
94
|
records: response.records,
|
|
89
95
|
selectedDomainId: dataArg.domainId,
|
|
90
96
|
error: null,
|
|
91
97
|
};
|
|
92
98
|
} catch (error: unknown) {
|
|
99
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
93
100
|
return {
|
|
94
|
-
...
|
|
101
|
+
...statePartArg.getState()!,
|
|
95
102
|
error: error instanceof Error ? error.message : 'Failed to fetch DNS records',
|
|
96
103
|
};
|
|
97
104
|
}
|
|
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import * as interfaces from '../../ts_interfaces/index.js';
|
|
3
3
|
import { appState } from './shared.js';
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
|
+
import { captureRealtimeTicket, isRealtimeTicketCurrent } from './realtime.js';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
// ============================================================================
|
|
@@ -20,6 +21,7 @@ export type TEmailDomainOperationName =
|
|
|
20
21
|
export interface IEmailDomainOperationState extends interfaces.requests.IEmailDomainActionResult {
|
|
21
22
|
operation: TEmailDomainOperationName;
|
|
22
23
|
completedAt: number;
|
|
24
|
+
domain?: interfaces.data.IEmailDomain;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface IEmailDomainsState {
|
|
@@ -46,7 +48,9 @@ export const emailDomainsStatePart = await appState.getStatePart<IEmailDomainsSt
|
|
|
46
48
|
|
|
47
49
|
const createOperationState = (
|
|
48
50
|
operationArg: TEmailDomainOperationName,
|
|
49
|
-
responseArg: interfaces.requests.IEmailDomainActionResult
|
|
51
|
+
responseArg: interfaces.requests.IEmailDomainActionResult & {
|
|
52
|
+
domain?: interfaces.data.IEmailDomain;
|
|
53
|
+
},
|
|
50
54
|
): IEmailDomainOperationState => ({
|
|
51
55
|
operation: operationArg,
|
|
52
56
|
completedAt: Date.now(),
|
|
@@ -120,6 +124,7 @@ export const fetchEmailDomainsAction = emailDomainsStatePart.createAction(
|
|
|
120
124
|
const context = getActionContext();
|
|
121
125
|
const currentState = statePartArg.getState()!;
|
|
122
126
|
if (!context.identity) return currentState;
|
|
127
|
+
const ticket = captureRealtimeTicket('emailDomains');
|
|
123
128
|
|
|
124
129
|
try {
|
|
125
130
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -130,8 +135,9 @@ export const fetchEmailDomainsAction = emailDomainsStatePart.createAction(
|
|
|
130
135
|
>('/typedrequest', 'getEmailServerSettings');
|
|
131
136
|
const response = await request.fire({ identity: context.identity });
|
|
132
137
|
const settingsResponse = await settingsRequest.fire({ identity: context.identity });
|
|
138
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
133
139
|
return {
|
|
134
|
-
...
|
|
140
|
+
...statePartArg.getState()!,
|
|
135
141
|
domains: response.domains,
|
|
136
142
|
settings: settingsResponse.settings,
|
|
137
143
|
isLoading: false,
|
|
@@ -139,8 +145,9 @@ export const fetchEmailDomainsAction = emailDomainsStatePart.createAction(
|
|
|
139
145
|
lastUpdated: Date.now(),
|
|
140
146
|
};
|
|
141
147
|
} catch (error: unknown) {
|
|
148
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
142
149
|
return {
|
|
143
|
-
...
|
|
150
|
+
...statePartArg.getState()!,
|
|
144
151
|
isLoading: false,
|
|
145
152
|
error: error instanceof Error ? error.message : 'Failed to fetch email domains',
|
|
146
153
|
};
|
|
@@ -169,14 +176,16 @@ export const createEmailDomainAction = emailDomainsStatePart.createAction<{
|
|
|
169
176
|
>('/typedrequest', 'createEmailDomain');
|
|
170
177
|
const response = await request.fire({ identity: context.identity, ...args });
|
|
171
178
|
const operation = createOperationState('create', response);
|
|
172
|
-
if (!response.success) {
|
|
173
|
-
return applyOperationState(currentState, operation, 'Failed to create email domain');
|
|
174
|
-
}
|
|
175
179
|
const refreshedState = await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
176
|
-
return applyOperationState(
|
|
180
|
+
return applyOperationState(
|
|
181
|
+
refreshedState,
|
|
182
|
+
operation,
|
|
183
|
+
response.success ? 'Email domain created' : 'Failed to create email domain',
|
|
184
|
+
);
|
|
177
185
|
} catch (error: unknown) {
|
|
178
186
|
const operation = createFailedOperationState('create', error, 'Failed to create email domain');
|
|
179
|
-
|
|
187
|
+
const refreshedState = await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
188
|
+
return applyOperationState(refreshedState, operation, 'Failed to create email domain');
|
|
180
189
|
}
|
|
181
190
|
});
|
|
182
191
|
|
|
@@ -260,27 +269,50 @@ export const deleteEmailDomainAction = emailDomainsStatePart.createAction<string
|
|
|
260
269
|
);
|
|
261
270
|
|
|
262
271
|
export const validateEmailDomainAction = emailDomainsStatePart.createAction<string>(
|
|
263
|
-
async (statePartArg, id
|
|
272
|
+
async (statePartArg, id) => {
|
|
264
273
|
const context = getActionContext();
|
|
265
274
|
const currentState = statePartArg.getState()!;
|
|
266
275
|
if (!context.identity) {
|
|
267
276
|
const operation = createFailedOperationState('validate', new Error('Not authenticated'), 'Not authenticated');
|
|
268
277
|
return applyOperationState(currentState, operation, 'Failed to validate email domain DNS');
|
|
269
278
|
}
|
|
279
|
+
|
|
280
|
+
const ticket = captureRealtimeTicket('emailDomains');
|
|
270
281
|
try {
|
|
271
282
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
272
283
|
interfaces.requests.IReq_ValidateEmailDomain
|
|
273
284
|
>('/typedrequest', 'validateEmailDomain');
|
|
274
285
|
const response = await request.fire({ identity: context.identity, id });
|
|
275
286
|
const operation = createOperationState('validate', response);
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
let latestState = statePartArg.getState()!;
|
|
288
|
+
|
|
289
|
+
if (response.domain && isRealtimeTicketCurrent(ticket)) {
|
|
290
|
+
const existingIndex = latestState.domains.findIndex((domain) => domain.id === response.domain!.id);
|
|
291
|
+
const domains = [...latestState.domains];
|
|
292
|
+
if (existingIndex >= 0) {
|
|
293
|
+
domains[existingIndex] = response.domain;
|
|
294
|
+
} else {
|
|
295
|
+
domains.push(response.domain);
|
|
296
|
+
}
|
|
297
|
+
latestState = {
|
|
298
|
+
...latestState,
|
|
299
|
+
domains,
|
|
300
|
+
lastUpdated: Date.now(),
|
|
301
|
+
};
|
|
278
302
|
}
|
|
279
|
-
|
|
280
|
-
return applyOperationState(
|
|
303
|
+
|
|
304
|
+
return applyOperationState(
|
|
305
|
+
latestState,
|
|
306
|
+
operation,
|
|
307
|
+
response.success ? 'Email domain DNS validated' : 'Failed to validate email domain DNS',
|
|
308
|
+
);
|
|
281
309
|
} catch (error: unknown) {
|
|
282
310
|
const operation = createFailedOperationState('validate', error, 'Failed to validate email domain DNS');
|
|
283
|
-
return applyOperationState(
|
|
311
|
+
return applyOperationState(
|
|
312
|
+
statePartArg.getState()!,
|
|
313
|
+
operation,
|
|
314
|
+
'Failed to validate email domain DNS',
|
|
315
|
+
);
|
|
284
316
|
}
|
|
285
317
|
},
|
|
286
318
|
);
|
|
@@ -315,6 +347,28 @@ export const provisionEmailDomainDnsAction = emailDomainsStatePart.createAction<
|
|
|
315
347
|
// Email Domain Standalone Functions
|
|
316
348
|
// ============================================================================
|
|
317
349
|
|
|
350
|
+
export async function fetchEmailDomainsSnapshot(): Promise<interfaces.data.IEmailDomain[]> {
|
|
351
|
+
const context = getActionContext();
|
|
352
|
+
if (!context.identity) throw new Error('Not authenticated');
|
|
353
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
354
|
+
interfaces.requests.IReq_GetEmailDomains
|
|
355
|
+
>('/typedrequest', 'getEmailDomains');
|
|
356
|
+
const response = await request.fire({ identity: context.identity });
|
|
357
|
+
return response.domains;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export async function fetchEmailDomainSnapshot(
|
|
361
|
+
idArg: string,
|
|
362
|
+
): Promise<interfaces.data.IEmailDomain | null> {
|
|
363
|
+
const context = getActionContext();
|
|
364
|
+
if (!context.identity) throw new Error('Not authenticated');
|
|
365
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
366
|
+
interfaces.requests.IReq_GetEmailDomain
|
|
367
|
+
>('/typedrequest', 'getEmailDomain');
|
|
368
|
+
const response = await request.fire({ identity: context.identity, id: idArg });
|
|
369
|
+
return response.domain;
|
|
370
|
+
}
|
|
371
|
+
|
|
318
372
|
export async function fetchEmailDomainDnsRecords(id: string) {
|
|
319
373
|
const context = getActionContext();
|
|
320
374
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import * as interfaces from '../../ts_interfaces/index.js';
|
|
3
3
|
import { appState } from './shared.js';
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
|
+
import { captureRealtimeTicket, isRealtimeTicketCurrent } from './realtime.js';
|
|
5
6
|
|
|
6
7
|
export interface IEmailOpsState {
|
|
7
8
|
emails: interfaces.requests.IEmail[];
|
|
@@ -29,6 +30,7 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
|
|
|
29
30
|
const context = getActionContext();
|
|
30
31
|
const currentState = statePartArg.getState()!;
|
|
31
32
|
if (!context.identity) return currentState;
|
|
33
|
+
const ticket = captureRealtimeTicket(['emailQueue', 'smtpAttempts']);
|
|
32
34
|
|
|
33
35
|
try {
|
|
34
36
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -38,16 +40,19 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
|
|
|
38
40
|
const response = await request.fire({
|
|
39
41
|
identity: context.identity,
|
|
40
42
|
});
|
|
43
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
41
44
|
|
|
42
45
|
return {
|
|
46
|
+
...statePartArg.getState()!,
|
|
43
47
|
emails: response.emails,
|
|
44
48
|
isLoading: false,
|
|
45
49
|
error: null,
|
|
46
50
|
lastUpdated: Date.now(),
|
|
47
51
|
};
|
|
48
52
|
} catch (error) {
|
|
53
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
49
54
|
return {
|
|
50
|
-
...
|
|
55
|
+
...statePartArg.getState()!,
|
|
51
56
|
isLoading: false,
|
|
52
57
|
error: error instanceof Error ? error.message : 'Failed to fetch emails',
|
|
53
58
|
};
|
|
@@ -55,3 +60,17 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
|
|
|
55
60
|
});
|
|
56
61
|
|
|
57
62
|
// ============================================================================
|
|
63
|
+
export async function fetchEmailDetailSnapshot(
|
|
64
|
+
emailIdArg: string,
|
|
65
|
+
): Promise<interfaces.requests.IEmailDetail | null> {
|
|
66
|
+
const context = getActionContext();
|
|
67
|
+
if (!context.identity) return null;
|
|
68
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
69
|
+
interfaces.requests.IReq_GetEmailDetail
|
|
70
|
+
>('/typedrequest', 'getEmailDetail');
|
|
71
|
+
const response = await request.fire({
|
|
72
|
+
identity: context.identity,
|
|
73
|
+
emailId: emailIdArg,
|
|
74
|
+
});
|
|
75
|
+
return response.email;
|
|
76
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as interfaces from '../../ts_interfaces/index.js';
|
|
2
|
+
|
|
3
|
+
export interface IOpsRealtimeTicket {
|
|
4
|
+
serverEpoch: string | null;
|
|
5
|
+
revisions: Partial<interfaces.requests.IOpsRealtimeRevisionMap>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type TOpsRealtimeListener = (
|
|
9
|
+
invalidationsArg: interfaces.requests.IOpsRealtimeInvalidation[],
|
|
10
|
+
) => void;
|
|
11
|
+
|
|
12
|
+
const emptyRevisions = (): interfaces.requests.IOpsRealtimeRevisionMap => {
|
|
13
|
+
return Object.fromEntries(
|
|
14
|
+
interfaces.requests.opsRealtimeResources.map((resource) => [resource, 0]),
|
|
15
|
+
) as unknown as interfaces.requests.IOpsRealtimeRevisionMap;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Orders pushes and HTTP reads. A read may apply only while every captured
|
|
20
|
+
* resource revision is still current.
|
|
21
|
+
*/
|
|
22
|
+
export class OpsRealtimeFreshness {
|
|
23
|
+
private serverEpoch: string | null = null;
|
|
24
|
+
private revisions = emptyRevisions();
|
|
25
|
+
private listeners = new Set<TOpsRealtimeListener>();
|
|
26
|
+
|
|
27
|
+
public getKnownState(): {
|
|
28
|
+
knownServerEpoch?: string;
|
|
29
|
+
knownRevisions: interfaces.requests.IOpsRealtimeRevisionMap;
|
|
30
|
+
} {
|
|
31
|
+
return {
|
|
32
|
+
knownServerEpoch: this.serverEpoch || undefined,
|
|
33
|
+
knownRevisions: { ...this.revisions },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public capture(
|
|
38
|
+
resourcesArg:
|
|
39
|
+
| interfaces.requests.TOpsRealtimeResource
|
|
40
|
+
| interfaces.requests.TOpsRealtimeResource[],
|
|
41
|
+
): IOpsRealtimeTicket {
|
|
42
|
+
const resources = Array.isArray(resourcesArg) ? resourcesArg : [resourcesArg];
|
|
43
|
+
return {
|
|
44
|
+
serverEpoch: this.serverEpoch,
|
|
45
|
+
revisions: Object.fromEntries(
|
|
46
|
+
resources.map((resource) => [resource, this.revisions[resource]]),
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public isCurrent(ticketArg: IOpsRealtimeTicket): boolean {
|
|
52
|
+
if (ticketArg.serverEpoch !== this.serverEpoch) return false;
|
|
53
|
+
return Object.entries(ticketArg.revisions).every(([resource, revision]) => {
|
|
54
|
+
return this.revisions[resource as interfaces.requests.TOpsRealtimeResource] === revision;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public accept(
|
|
59
|
+
serverEpochArg: string,
|
|
60
|
+
invalidationsArg: interfaces.requests.IOpsRealtimeInvalidation[],
|
|
61
|
+
): interfaces.requests.IOpsRealtimeInvalidation[] {
|
|
62
|
+
const epochChanged = this.serverEpoch !== serverEpochArg;
|
|
63
|
+
if (epochChanged) {
|
|
64
|
+
this.serverEpoch = serverEpochArg;
|
|
65
|
+
this.revisions = emptyRevisions();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const accepted: interfaces.requests.IOpsRealtimeInvalidation[] = [];
|
|
69
|
+
for (const invalidation of invalidationsArg) {
|
|
70
|
+
if (!interfaces.requests.opsRealtimeResources.includes(invalidation.resource)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (invalidation.revision <= this.revisions[invalidation.resource]) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
this.revisions[invalidation.resource] = invalidation.revision;
|
|
77
|
+
accepted.push(invalidation);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (accepted.length > 0) {
|
|
81
|
+
for (const listener of Array.from(this.listeners)) {
|
|
82
|
+
listener(accepted);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return accepted;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public subscribe(listenerArg: TOpsRealtimeListener): () => void {
|
|
89
|
+
this.listeners.add(listenerArg);
|
|
90
|
+
return () => this.listeners.delete(listenerArg);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public reset(): void {
|
|
94
|
+
this.serverEpoch = null;
|
|
95
|
+
this.revisions = emptyRevisions();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const opsRealtimeFreshness = new OpsRealtimeFreshness();
|
|
100
|
+
|
|
101
|
+
export const captureRealtimeTicket = (
|
|
102
|
+
resourcesArg:
|
|
103
|
+
| interfaces.requests.TOpsRealtimeResource
|
|
104
|
+
| interfaces.requests.TOpsRealtimeResource[],
|
|
105
|
+
): IOpsRealtimeTicket => opsRealtimeFreshness.capture(resourcesArg);
|
|
106
|
+
|
|
107
|
+
export const isRealtimeTicketCurrent = (ticketArg: IOpsRealtimeTicket): boolean => {
|
|
108
|
+
return opsRealtimeFreshness.isCurrent(ticketArg);
|
|
109
|
+
};
|
|
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import * as interfaces from '../../ts_interfaces/index.js';
|
|
3
3
|
import { appState } from './shared.js';
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
|
+
import { captureRealtimeTicket, isRealtimeTicketCurrent } from './realtime.js';
|
|
5
6
|
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// Remote Ingress State
|
|
@@ -52,6 +53,7 @@ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(asyn
|
|
|
52
53
|
const context = getActionContext();
|
|
53
54
|
const currentState = statePartArg.getState()!;
|
|
54
55
|
if (!context.identity) return currentState;
|
|
56
|
+
const ticket = captureRealtimeTicket('edges');
|
|
55
57
|
|
|
56
58
|
try {
|
|
57
59
|
const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -71,9 +73,10 @@ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(asyn
|
|
|
71
73
|
statusRequest.fire({ identity: context.identity }),
|
|
72
74
|
hubSettingsRequest.fire({ identity: context.identity }),
|
|
73
75
|
]);
|
|
76
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
74
77
|
|
|
75
78
|
return {
|
|
76
|
-
...
|
|
79
|
+
...statePartArg.getState()!,
|
|
77
80
|
edges: edgesResponse.edges,
|
|
78
81
|
statuses: statusResponse.statuses,
|
|
79
82
|
hubSettings: hubSettingsResponse.settings,
|
|
@@ -82,8 +85,9 @@ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(asyn
|
|
|
82
85
|
lastUpdated: Date.now(),
|
|
83
86
|
};
|
|
84
87
|
} catch (error) {
|
|
88
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
85
89
|
return {
|
|
86
|
-
...
|
|
90
|
+
...statePartArg.getState()!,
|
|
87
91
|
isLoading: false,
|
|
88
92
|
error: error instanceof Error ? error.message : 'Failed to fetch remote ingress data',
|
|
89
93
|
};
|
|
@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import * as interfaces from '../../ts_interfaces/index.js';
|
|
3
3
|
import { appState } from './shared.js';
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
|
+
import { captureRealtimeTicket, isRealtimeTicketCurrent } from './realtime.js';
|
|
5
6
|
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// Route Management State
|
|
@@ -40,6 +41,7 @@ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(asy
|
|
|
40
41
|
const context = getActionContext();
|
|
41
42
|
const currentState = statePartArg.getState()!;
|
|
42
43
|
if (!context.identity) return currentState;
|
|
44
|
+
const ticket = captureRealtimeTicket('routes');
|
|
43
45
|
|
|
44
46
|
try {
|
|
45
47
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -49,9 +51,10 @@ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(asy
|
|
|
49
51
|
const response = await request.fire({
|
|
50
52
|
identity: context.identity,
|
|
51
53
|
});
|
|
54
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
52
55
|
|
|
53
56
|
return {
|
|
54
|
-
...
|
|
57
|
+
...statePartArg.getState()!,
|
|
55
58
|
mergedRoutes: response.routes,
|
|
56
59
|
warnings: response.warnings,
|
|
57
60
|
isLoading: false,
|
|
@@ -59,8 +62,9 @@ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(asy
|
|
|
59
62
|
lastUpdated: Date.now(),
|
|
60
63
|
};
|
|
61
64
|
} catch (error) {
|
|
65
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
62
66
|
return {
|
|
63
|
-
...
|
|
67
|
+
...statePartArg.getState()!,
|
|
64
68
|
isLoading: false,
|
|
65
69
|
error: error instanceof Error ? error.message : 'Failed to fetch routes',
|
|
66
70
|
};
|
|
@@ -71,6 +75,7 @@ export const fetchHttpRedirectsAction = routeManagementStatePart.createAction(as
|
|
|
71
75
|
const context = getActionContext();
|
|
72
76
|
const currentState = statePartArg.getState()!;
|
|
73
77
|
if (!context.identity) return currentState;
|
|
78
|
+
const ticket = captureRealtimeTicket('routes');
|
|
74
79
|
|
|
75
80
|
try {
|
|
76
81
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -80,17 +85,19 @@ export const fetchHttpRedirectsAction = routeManagementStatePart.createAction(as
|
|
|
80
85
|
const response = await request.fire({
|
|
81
86
|
identity: context.identity,
|
|
82
87
|
});
|
|
88
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
83
89
|
|
|
84
90
|
return {
|
|
85
|
-
...
|
|
91
|
+
...statePartArg.getState()!,
|
|
86
92
|
httpRedirects: response.redirects,
|
|
87
93
|
isLoading: false,
|
|
88
94
|
error: null,
|
|
89
95
|
lastUpdated: Date.now(),
|
|
90
96
|
};
|
|
91
97
|
} catch (error) {
|
|
98
|
+
if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!;
|
|
92
99
|
return {
|
|
93
|
-
...
|
|
100
|
+
...statePartArg.getState()!,
|
|
94
101
|
isLoading: false,
|
|
95
102
|
error: error instanceof Error ? error.message : 'Failed to fetch HTTP redirects',
|
|
96
103
|
};
|