@serve.zone/dcrouter 17.1.5 → 17.1.7
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/deno.json +1 -1
- package/dist_serve/bundle.js +559 -559
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +2 -2
- package/dist_ts/dns/manager.dns.js +23 -6
- package/dist_ts/email/classes.mail-dns-sync.d.ts +18 -0
- package/dist_ts/email/classes.mail-dns-sync.js +101 -2
- package/dist_ts/monitoring/classes.metricscache.d.ts +2 -0
- package/dist_ts/monitoring/classes.metricscache.js +28 -6
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +10 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +99 -31
- package/dist_ts/opsserver/handlers/security.handler.js +24 -6
- package/dist_ts/opsserver/handlers/stats.handler.js +2 -3
- package/dist_ts/remoteingress/classes.hub-lifecycle.js +2 -2
- package/dist_ts_interfaces/requests/stats.d.ts +7 -0
- package/dist_ts_migrations/index.js +285 -152
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/network.js +11 -2
- package/dist_ts_web/appstate/ui.js +1 -8
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +3 -1
- package/ts/dns/manager.dns.ts +22 -5
- package/ts/email/classes.mail-dns-sync.ts +105 -1
- package/ts/monitoring/classes.metricscache.ts +36 -8
- package/ts/monitoring/classes.metricsmanager.ts +113 -32
- package/ts/opsserver/handlers/security.handler.ts +35 -6
- package/ts/opsserver/handlers/stats.handler.ts +1 -2
- package/ts/remoteingress/classes.hub-lifecycle.ts +3 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/network.ts +12 -1
- package/ts_web/appstate/ui.ts +0 -9
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/dcrouter',
|
|
6
|
-
version: '17.1.
|
|
6
|
+
version: '17.1.7',
|
|
7
7
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
|
|
@@ -3,6 +3,8 @@ import * as interfaces from '../../dist_ts_interfaces/index.js';
|
|
|
3
3
|
import { appState } from './shared.js';
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
5
|
import { runBackgroundRefresh } from './shared.js';
|
|
6
|
+
const IP_INTELLIGENCE_REFRESH_INTERVAL_MS = 60_000;
|
|
7
|
+
let lastIpIntelligenceRefreshAt = 0;
|
|
6
8
|
export const networkStatePart = await appState.getStatePart('network', {
|
|
7
9
|
connections: [],
|
|
8
10
|
connectionsByIP: {},
|
|
@@ -25,9 +27,16 @@ export const networkStatePart = await appState.getStatePart('network', {
|
|
|
25
27
|
error: null,
|
|
26
28
|
}, 'soft');
|
|
27
29
|
export function refreshNetworkIpIntelligence(identity, ipAddresses) {
|
|
28
|
-
const ips = [...new Set(ipAddresses.filter(Boolean))]
|
|
30
|
+
const ips = [...new Set(ipAddresses.map((ip) => ip.trim()).filter(Boolean))]
|
|
31
|
+
.sort()
|
|
32
|
+
.slice(0, 100);
|
|
29
33
|
if (ips.length === 0)
|
|
30
34
|
return;
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
if (now - lastIpIntelligenceRefreshAt < IP_INTELLIGENCE_REFRESH_INTERVAL_MS) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
lastIpIntelligenceRefreshAt = now;
|
|
31
40
|
runBackgroundRefresh('networkIpIntelligence', 'IP intelligence refresh failed:', async () => {
|
|
32
41
|
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest('/typedrequest', 'listIpIntelligence');
|
|
33
42
|
const intelligenceResponse = await intelligenceRequest.fire({
|
|
@@ -119,4 +128,4 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|
|
119
128
|
}
|
|
120
129
|
});
|
|
121
130
|
// ============================================================================
|
|
122
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
131
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29yay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzX3dlYi9hcHBzdGF0ZS9uZXR3b3JrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxVQUFVLE1BQU0sOEJBQThCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN2QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDOUMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRW5ELE1BQU0sbUNBQW1DLEdBQUcsTUFBTSxDQUFDO0FBQ25ELElBQUksMkJBQTJCLEdBQUcsQ0FBQyxDQUFDO0FBd0JwQyxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLFFBQVEsQ0FBQyxZQUFZLENBQ3pELFNBQVMsRUFDVDtJQUNFLFdBQVcsRUFBRSxFQUFFO0lBQ2YsZUFBZSxFQUFFLEVBQUU7SUFDbkIsY0FBYyxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsQ0FBQyxFQUFFLGlCQUFpQixFQUFFLENBQUMsRUFBRTtJQUM3RCxVQUFVLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDLEVBQUU7SUFDN0IsTUFBTSxFQUFFLEVBQUU7SUFDVixpQkFBaUIsRUFBRSxFQUFFO0lBQ3JCLE9BQU8sRUFBRSxFQUFFO0lBQ1gsY0FBYyxFQUFFLEVBQUU7SUFDbEIsY0FBYyxFQUFFLEVBQUU7SUFDbEIsY0FBYyxFQUFFLEVBQUU7SUFDbEIsaUJBQWlCLEVBQUUsRUFBRTtJQUNyQixpQkFBaUIsRUFBRSxDQUFDO0lBQ3BCLGFBQWEsRUFBRSxDQUFDO0lBQ2hCLFFBQVEsRUFBRSxFQUFFO0lBQ1osaUJBQWlCLEVBQUUsSUFBSTtJQUN2QixnQkFBZ0IsRUFBRSxJQUFJO0lBQ3RCLFdBQVcsRUFBRSxD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
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { appState } from './shared.js';
|
|
2
|
-
import { networkStatePart, fetchNetworkStatsAction } from './network.js';
|
|
3
2
|
import { certificateStatePart, fetchCertificateOverviewAction } from './certificates.js';
|
|
4
3
|
// Determine initial view from URL path
|
|
5
4
|
const getInitialView = () => {
|
|
@@ -34,12 +33,6 @@ export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePart
|
|
|
34
33
|
// Set Active View Action
|
|
35
34
|
export const setActiveViewAction = uiStatePart.createAction(async (statePartArg, viewName) => {
|
|
36
35
|
const currentState = statePartArg.getState();
|
|
37
|
-
// If switching to network view, ensure we fetch network data
|
|
38
|
-
if (viewName === 'network' && currentState.activeView !== 'network') {
|
|
39
|
-
setTimeout(() => {
|
|
40
|
-
networkStatePart.dispatchAction(fetchNetworkStatsAction, null);
|
|
41
|
-
}, 100);
|
|
42
|
-
}
|
|
43
36
|
// If switching to the Domains group, ensure we fetch certificate data
|
|
44
37
|
// (Certificates is a subview of Domains).
|
|
45
38
|
if (viewName === 'domains' && currentState.activeView !== 'domains') {
|
|
@@ -52,4 +45,4 @@ export const setActiveViewAction = uiStatePart.createAction(async (statePartArg,
|
|
|
52
45
|
activeView: viewName,
|
|
53
46
|
};
|
|
54
47
|
});
|
|
55
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
48
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidWkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90c193ZWIvYXBwc3RhdGUvdWkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN2QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsOEJBQThCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQVd6Rix1Q0FBdUM7QUFDdkMsTUFBTSxjQUFjLEdBQUcsR0FBVyxFQUFFO0lBQ2xDLE1BQU0sSUFBSSxHQUFHLE9BQU8sTUFBTSxLQUFLLFdBQVcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztJQUM1RSxNQUFNLFVBQVUsR0FBRyxDQUFDLFVBQVUsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQzdGLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ2pELE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN6QixPQUFPLFVBQVUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDO0FBQ3ZELENBQUMsQ0FBQztBQUVGLCtEQUErRDtBQUMvRCxNQUFNLGlCQUFpQixHQUFHLEdBQWtCLEVBQUU7SUFDNUMsTUFBTSxJQUFJLEdBQUcsT0FBTyxNQUFNLEtBQUssV0FBVyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO0lBQzVFLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ2pELE9BQU8sUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQztBQUM3QixDQUFDLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsTUFBTSxRQUFRLENBQUMsWUFBWSxDQUNwRCxJQUFJLEVBQ0o7SUFDRSxVQUFVLEVBQUUsY0FBYyxFQUFFO0lBQzVCLGFBQWEsRUFBRSxpQkFBaUIsRUFBRTtJQUNsQyxnQkFBZ0IsRUFBRSxLQUFLO0lBQ3ZCLFdBQVcsRUFBRSxJQUFJO0lBQ2pCLGVBQWUsRUFBRSxJQUFJLEVBQUUsV0FBVztJQUNsQyxLQUFLLEVBQUUsT0FBTztDQUNmLENBQ0YsQ0FBQztBQUVGLDZCQUE2QjtBQUM3QixNQUFNLENBQUMsTUFBTSx1QkFBdUIsR0FBRyxXQUFXLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBRSxZQUFZLEVBQXFCLEVBQUU7SUFDeEcsTUFBTSxZQUFZLEdBQUcsWUFBWSxDQUFDLFFBQVEsRUFBRyxDQUFDO0lBQzlDLE9BQU87UUFDTCxHQUFHLFlBQVk7UUFDZixXQUFXLEVBQUUsQ0FBQyxZQUFZLENBQUMsV0FBVztLQUN2QyxDQUFDO0FBQ0osQ0FBQyxDQUFDLENBQUM7QUFFSCx5QkFBeUI7QUFDekIsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsV0FBVyxDQUFDLFlBQVksQ0FBUyxLQUFLLEVBQUUsWUFBWSxFQUFFLFFBQVEsRUFBcUIsRUFBRTtJQUN0SCxNQUFNLFlBQVksR0FBRyxZQUFZLENBQUMsUUFBUSxFQUFHLENBQUM7SUFFOUMsc0VBQXNFO0lBQ3RFLDBDQUEwQztJQUMxQyxJQUFJLFFBQVEsS0FBSyxTQUFTLElBQUksWUFBWSxDQUFDLFVBQVUsS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUNwRSxVQUFVLENBQUMsR0FBRyxFQUFFO1lBQ2Qsb0JBQW9CLENBQUMsY0FBYyxDQUFDLDhCQUE4QixFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzVFLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUNWLENBQUM7SUFFRCxPQUFPO1FBQ0wsR0FBRyxZQUFZO1FBQ2YsVUFBVSxFQUFFLFFBQVE7S0FDckIsQ0FBQztBQUNKLENBQUMsQ0FBQyxDQUFDIn0=
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/dcrouter",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.7",
|
|
5
5
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@push.rocks/smartnetwork": "^4.8.0",
|
|
54
54
|
"@push.rocks/smartpath": "^6.0.0",
|
|
55
55
|
"@push.rocks/smartpromise": "^4.2.4",
|
|
56
|
-
"@push.rocks/smartproxy": "^27.
|
|
56
|
+
"@push.rocks/smartproxy": "^27.18.0",
|
|
57
57
|
"@push.rocks/smartradius": "^1.3.0",
|
|
58
58
|
"@push.rocks/smartrequest": "^5.0.4",
|
|
59
59
|
"@push.rocks/smartrx": "^3.0.12",
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -590,7 +590,9 @@ export class DcRouter {
|
|
|
590
590
|
.withStart(async () => {
|
|
591
591
|
this.remoteIngressManager = new RemoteIngressManager();
|
|
592
592
|
this.remoteIngressManager.setHubPublicIps([this.options.publicIp]);
|
|
593
|
-
this.remoteIngressManager.setOnEdgesChanged((reason) =>
|
|
593
|
+
this.remoteIngressManager.setOnEdgesChanged((reason) => (
|
|
594
|
+
this.mailDnsSync.requestEdgeEligibilityCheck(reason)
|
|
595
|
+
));
|
|
594
596
|
await this.remoteIngressManager.initialize();
|
|
595
597
|
})
|
|
596
598
|
.withStop(async () => {
|
package/ts/dns/manager.dns.ts
CHANGED
|
@@ -690,32 +690,49 @@ export class DnsManager {
|
|
|
690
690
|
localByProviderId.set(providerId, record);
|
|
691
691
|
}
|
|
692
692
|
const remaining = new Set(existing);
|
|
693
|
+
let mirrorChanged = false;
|
|
693
694
|
for (let index = 0; index < providerRecords.length; index++) {
|
|
694
695
|
const pr = providerRecords[index];
|
|
695
696
|
const providerId = providerIds[index];
|
|
696
697
|
const match = localByProviderId.get(providerId);
|
|
697
698
|
if (!match) {
|
|
698
699
|
await this.createSyncedRecord(id, { ...pr, providerRecordId: providerId }, doc.createdBy);
|
|
700
|
+
mirrorChanged = true;
|
|
699
701
|
continue;
|
|
700
702
|
}
|
|
701
703
|
remaining.delete(match);
|
|
702
|
-
|
|
704
|
+
const providerName = pr.name.toLowerCase();
|
|
705
|
+
const providerProxied = pr.proxied;
|
|
706
|
+
const changed = match.name !== providerName
|
|
707
|
+
|| match.type !== pr.type
|
|
708
|
+
|| match.value !== pr.value
|
|
709
|
+
|| match.ttl !== pr.ttl
|
|
710
|
+
|| (match.proxied ?? false) !== (providerProxied ?? false)
|
|
711
|
+
|| match.source !== 'synced'
|
|
712
|
+
|| match.providerRecordId !== providerId;
|
|
713
|
+
if (!changed) continue;
|
|
714
|
+
|
|
715
|
+
match.name = providerName;
|
|
703
716
|
match.type = pr.type;
|
|
704
717
|
match.value = pr.value;
|
|
705
718
|
match.ttl = pr.ttl;
|
|
706
|
-
match.proxied =
|
|
719
|
+
match.proxied = providerProxied;
|
|
707
720
|
match.source = 'synced';
|
|
708
721
|
match.providerRecordId = providerId;
|
|
709
722
|
match.updatedAt = Date.now();
|
|
710
723
|
await match.save();
|
|
724
|
+
mirrorChanged = true;
|
|
711
725
|
}
|
|
712
726
|
for (const staleMirror of remaining) {
|
|
713
727
|
if (staleMirror.source !== 'synced' || !staleMirror.providerRecordId?.trim()) continue;
|
|
714
728
|
await staleMirror.delete();
|
|
729
|
+
mirrorChanged = true;
|
|
730
|
+
}
|
|
731
|
+
if (mirrorChanged) {
|
|
732
|
+
doc.lastSyncedAt = Date.now();
|
|
733
|
+
doc.updatedAt = doc.lastSyncedAt;
|
|
734
|
+
await doc.save();
|
|
715
735
|
}
|
|
716
|
-
doc.lastSyncedAt = Date.now();
|
|
717
|
-
doc.updatedAt = doc.lastSyncedAt;
|
|
718
|
-
await doc.save();
|
|
719
736
|
return {
|
|
720
737
|
success: true,
|
|
721
738
|
recordCount: providerRecords.length,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
MailEdgeEligibility,
|
|
19
19
|
ReleasedRemoteIngressEgressIdentitySource,
|
|
20
20
|
type IEligibleMailEdge,
|
|
21
|
+
type IMailEdgeEligibilityResult,
|
|
21
22
|
} from './classes.mail-edge-eligibility.js';
|
|
22
23
|
import {
|
|
23
24
|
evaluateMailTxtIntent,
|
|
@@ -33,6 +34,8 @@ export const MAIL_DNS_SYNC_CREATED_BY = 'mail-dns-reconciler';
|
|
|
33
34
|
export const MAIL_DNS_MANAGED_BY = 'mail-dns-reconciler';
|
|
34
35
|
export const MAIL_DNS_RETRY_INTERVAL_MS = 5 * 60_000;
|
|
35
36
|
export const MAIL_DNS_MAX_RETRY_INTERVAL_MS = 60 * 60_000;
|
|
37
|
+
export const MAIL_DNS_EDGE_ELIGIBILITY_DEBOUNCE_MS = 250;
|
|
38
|
+
export const MAIL_DNS_EDGE_ELIGIBILITY_COOLDOWN_MS = 5_000;
|
|
36
39
|
export const MAIL_DNS_PUBLIC_RESOLVER_OPTIONS = {
|
|
37
40
|
strategy: 'prefer-udp' as const,
|
|
38
41
|
allowDohFallback: true,
|
|
@@ -66,6 +69,28 @@ function normalizeHostname(value: string): string {
|
|
|
66
69
|
return value.trim().toLowerCase().replace(/\.$/, '');
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
function mailEdgeEligibilitySignature(result: IMailEdgeEligibilityResult): string {
|
|
73
|
+
const edges = result.edges.map((candidate) => {
|
|
74
|
+
const identities = candidate.identities.map((identity) => ({
|
|
75
|
+
family: identity.family,
|
|
76
|
+
address: identity.address,
|
|
77
|
+
heloHostname: normalizeHostname(identity.heloHostname),
|
|
78
|
+
proof: identity.proof,
|
|
79
|
+
}));
|
|
80
|
+
identities.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
81
|
+
return {
|
|
82
|
+
id: candidate.edge.id,
|
|
83
|
+
hostname: normalizeHostname(candidate.hostname),
|
|
84
|
+
tags: [...(candidate.edge.tags || [])].sort(),
|
|
85
|
+
publicIp: candidate.edge.publicIp || '',
|
|
86
|
+
publicIpV6: candidate.edge.publicIpV6 || '',
|
|
87
|
+
identities,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
edges.sort((left, right) => left.id.localeCompare(right.id));
|
|
91
|
+
return JSON.stringify({ supported: result.supported, edges });
|
|
92
|
+
}
|
|
93
|
+
|
|
69
94
|
function normalizeMx(value: string): string {
|
|
70
95
|
const [priority, exchange = ''] = value.trim().split(/\s+/);
|
|
71
96
|
return `${Number(priority)} ${normalizeHostname(exchange)}`;
|
|
@@ -359,6 +384,13 @@ export class MailDnsSync {
|
|
|
359
384
|
private forceAll = false;
|
|
360
385
|
private forcedDomainIds = new Set<string>();
|
|
361
386
|
private edgeEligibility: MailEdgeEligibility;
|
|
387
|
+
private edgeEligibilitySignature?: string;
|
|
388
|
+
private edgeEligibilityCheckTimer?: ReturnType<typeof setTimeout>;
|
|
389
|
+
private edgeEligibilityCheckPromise?: Promise<void>;
|
|
390
|
+
private pendingEdgeEligibilityReason?: string;
|
|
391
|
+
private lastEdgeEligibilitySyncAt = 0;
|
|
392
|
+
private edgeEligibilityDebounceMs = MAIL_DNS_EDGE_ELIGIBILITY_DEBOUNCE_MS;
|
|
393
|
+
private edgeEligibilityCooldownMs = MAIL_DNS_EDGE_ELIGIBILITY_COOLDOWN_MS;
|
|
362
394
|
|
|
363
395
|
constructor(
|
|
364
396
|
private dcRouterRef: DcRouter,
|
|
@@ -377,12 +409,18 @@ export class MailDnsSync {
|
|
|
377
409
|
}
|
|
378
410
|
this.started = true;
|
|
379
411
|
this.stopped = false;
|
|
412
|
+
this.edgeEligibilitySignature = undefined;
|
|
413
|
+
this.pendingEdgeEligibilityReason = undefined;
|
|
414
|
+
this.lastEdgeEligibilitySyncAt = 0;
|
|
380
415
|
this.queueRequest(undefined, true);
|
|
381
416
|
try {
|
|
382
417
|
await this.kick('startup');
|
|
383
418
|
} catch (error: unknown) {
|
|
384
419
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
385
420
|
this.retryTimer = undefined;
|
|
421
|
+
if (this.edgeEligibilityCheckTimer) clearTimeout(this.edgeEligibilityCheckTimer);
|
|
422
|
+
this.edgeEligibilityCheckTimer = undefined;
|
|
423
|
+
this.pendingEdgeEligibilityReason = undefined;
|
|
386
424
|
this.started = false;
|
|
387
425
|
this.dirty = false;
|
|
388
426
|
throw error;
|
|
@@ -397,6 +435,12 @@ export class MailDnsSync {
|
|
|
397
435
|
clearTimeout(this.retryTimer);
|
|
398
436
|
this.retryTimer = undefined;
|
|
399
437
|
}
|
|
438
|
+
if (this.edgeEligibilityCheckTimer) {
|
|
439
|
+
clearTimeout(this.edgeEligibilityCheckTimer);
|
|
440
|
+
this.edgeEligibilityCheckTimer = undefined;
|
|
441
|
+
}
|
|
442
|
+
this.pendingEdgeEligibilityReason = undefined;
|
|
443
|
+
await this.edgeEligibilityCheckPromise?.catch(() => undefined);
|
|
400
444
|
await this.runPromise?.catch(() => undefined);
|
|
401
445
|
this.verifier.destroy?.();
|
|
402
446
|
}
|
|
@@ -410,6 +454,65 @@ export class MailDnsSync {
|
|
|
410
454
|
});
|
|
411
455
|
}
|
|
412
456
|
|
|
457
|
+
/**
|
|
458
|
+
* RemoteIngress topology events are advisory. Resolve the actual mail-edge
|
|
459
|
+
* eligibility snapshot and only reconcile DNS when its semantic shape
|
|
460
|
+
* changes. Volatile heartbeat/observation timestamps never enter the
|
|
461
|
+
* signature.
|
|
462
|
+
*/
|
|
463
|
+
public requestEdgeEligibilityCheck(reason: string): void {
|
|
464
|
+
if (this.stopped || !this.started) return;
|
|
465
|
+
this.pendingEdgeEligibilityReason = reason;
|
|
466
|
+
if (this.edgeEligibilityCheckTimer || this.edgeEligibilityCheckPromise) return;
|
|
467
|
+
this.scheduleEdgeEligibilityCheck();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private scheduleEdgeEligibilityCheck(): void {
|
|
471
|
+
if (this.stopped || !this.started || !this.pendingEdgeEligibilityReason) return;
|
|
472
|
+
const cooldownRemaining = Math.max(
|
|
473
|
+
0,
|
|
474
|
+
this.lastEdgeEligibilitySyncAt + this.edgeEligibilityCooldownMs - Date.now(),
|
|
475
|
+
);
|
|
476
|
+
const delay = Math.max(this.edgeEligibilityDebounceMs, cooldownRemaining);
|
|
477
|
+
this.edgeEligibilityCheckTimer = setTimeout(() => {
|
|
478
|
+
this.edgeEligibilityCheckTimer = undefined;
|
|
479
|
+
this.runEdgeEligibilityCheck().catch((error: unknown) => {
|
|
480
|
+
logger.log('error', `MailDnsSync edge eligibility check failed: ${(error as Error).message}`);
|
|
481
|
+
});
|
|
482
|
+
}, delay);
|
|
483
|
+
this.edgeEligibilityCheckTimer.unref?.();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private async runEdgeEligibilityCheck(): Promise<void> {
|
|
487
|
+
if (this.edgeEligibilityCheckPromise) return await this.edgeEligibilityCheckPromise;
|
|
488
|
+
const reason = this.pendingEdgeEligibilityReason;
|
|
489
|
+
this.pendingEdgeEligibilityReason = undefined;
|
|
490
|
+
if (!reason || this.stopped || !this.started) return;
|
|
491
|
+
|
|
492
|
+
this.edgeEligibilityCheckPromise = (async () => {
|
|
493
|
+
const result = await this.edgeEligibility.resolveForDomain();
|
|
494
|
+
const nextSignature = mailEdgeEligibilitySignature(result);
|
|
495
|
+
const previousSignature = this.edgeEligibilitySignature;
|
|
496
|
+
this.edgeEligibilitySignature = nextSignature;
|
|
497
|
+
if (
|
|
498
|
+
previousSignature === undefined
|
|
499
|
+
|| previousSignature === nextSignature
|
|
500
|
+
|| this.stopped
|
|
501
|
+
|| !this.started
|
|
502
|
+
) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
this.lastEdgeEligibilitySyncAt = Date.now();
|
|
506
|
+
this.requestSync(reason);
|
|
507
|
+
})().finally(() => {
|
|
508
|
+
this.edgeEligibilityCheckPromise = undefined;
|
|
509
|
+
if (this.pendingEdgeEligibilityReason && !this.stopped && this.started) {
|
|
510
|
+
this.scheduleEdgeEligibilityCheck();
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
return await this.edgeEligibilityCheckPromise;
|
|
514
|
+
}
|
|
515
|
+
|
|
413
516
|
/** Coalescing entry point used by API actions that need the durable outcome. */
|
|
414
517
|
public async sync(reason: string, domainIds?: string[]): Promise<void> {
|
|
415
518
|
if (this.stopped) throw new Error('MailDnsSync is stopped');
|
|
@@ -502,7 +605,8 @@ export class MailDnsSync {
|
|
|
502
605
|
if (docs.length === 0) return;
|
|
503
606
|
|
|
504
607
|
// Refresh the broad operational status independently from per-domain pins.
|
|
505
|
-
await this.edgeEligibility.resolveForDomain();
|
|
608
|
+
const broadEligibility = await this.edgeEligibility.resolveForDomain();
|
|
609
|
+
this.edgeEligibilitySignature = mailEdgeEligibilitySignature(broadEligibility);
|
|
506
610
|
const plans: IPlannedDomain[] = [];
|
|
507
611
|
const deletingDocs: EmailDomainDoc[] = [];
|
|
508
612
|
for (const loadedDoc of docs) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export interface ICacheEntry<T> {
|
|
2
2
|
data: T;
|
|
3
3
|
timestamp: number;
|
|
4
|
+
ttl: number;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
export class MetricsCache {
|
|
7
8
|
private cache = new Map<string, ICacheEntry<any>>();
|
|
9
|
+
private inFlight = new Map<string, Promise<any>>();
|
|
8
10
|
private readonly defaultTTL: number;
|
|
9
11
|
|
|
10
12
|
constructor(defaultTTL: number = 500) {
|
|
@@ -19,20 +21,44 @@ export class MetricsCache {
|
|
|
19
21
|
const now = Date.now();
|
|
20
22
|
const actualTTL = ttl ?? this.defaultTTL;
|
|
21
23
|
|
|
22
|
-
if (cached && (now - cached.timestamp) <
|
|
24
|
+
if (cached && (now - cached.timestamp) < cached.ttl) {
|
|
23
25
|
return cached.data;
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
if (cached) {
|
|
29
|
+
this.cache.delete(key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pending = this.inFlight.get(key) as Promise<T> | undefined;
|
|
33
|
+
if (pending) {
|
|
34
|
+
return pending;
|
|
35
|
+
}
|
|
25
36
|
|
|
26
37
|
const result = computeFn();
|
|
27
38
|
|
|
28
39
|
// Handle both sync and async compute functions
|
|
29
40
|
if (result instanceof Promise) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
let inFlightPromise: Promise<T>;
|
|
42
|
+
inFlightPromise = result.then(
|
|
43
|
+
(data) => {
|
|
44
|
+
if (this.inFlight.get(key) === inFlightPromise) {
|
|
45
|
+
this.inFlight.delete(key);
|
|
46
|
+
this.cache.set(key, { data, timestamp: Date.now(), ttl: actualTTL });
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
},
|
|
50
|
+
(error: unknown) => {
|
|
51
|
+
if (this.inFlight.get(key) === inFlightPromise) {
|
|
52
|
+
this.inFlight.delete(key);
|
|
53
|
+
this.cache.delete(key);
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
this.inFlight.set(key, inFlightPromise);
|
|
59
|
+
return inFlightPromise;
|
|
34
60
|
} else {
|
|
35
|
-
this.cache.set(key, { data: result, timestamp: now });
|
|
61
|
+
this.cache.set(key, { data: result, timestamp: now, ttl: actualTTL });
|
|
36
62
|
return result;
|
|
37
63
|
}
|
|
38
64
|
}
|
|
@@ -42,6 +68,7 @@ export class MetricsCache {
|
|
|
42
68
|
*/
|
|
43
69
|
public invalidate(key: string): void {
|
|
44
70
|
this.cache.delete(key);
|
|
71
|
+
this.inFlight.delete(key);
|
|
45
72
|
}
|
|
46
73
|
|
|
47
74
|
/**
|
|
@@ -49,6 +76,7 @@ export class MetricsCache {
|
|
|
49
76
|
*/
|
|
50
77
|
public clear(): void {
|
|
51
78
|
this.cache.clear();
|
|
79
|
+
this.inFlight.clear();
|
|
52
80
|
}
|
|
53
81
|
|
|
54
82
|
/**
|
|
@@ -67,9 +95,9 @@ export class MetricsCache {
|
|
|
67
95
|
public cleanup(): void {
|
|
68
96
|
const now = Date.now();
|
|
69
97
|
for (const [key, entry] of this.cache.entries()) {
|
|
70
|
-
if (now - entry.timestamp >
|
|
98
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
71
99
|
this.cache.delete(key);
|
|
72
100
|
}
|
|
73
101
|
}
|
|
74
102
|
}
|
|
75
|
-
}
|
|
103
|
+
}
|