Fix Arvan mail A record value to array of IPs
Arvan CDN 4.0 rejects A creates when value is a single { ip } object
(422: value must be an array). Send [{ ip }] and compare listed A
values as arrays so ensureMailRecords no longer fails before DKIM.
This commit is contained in:
@@ -18,7 +18,8 @@ type ArvanRecordType = 'aname' | 'cname' | 'a' | 'mx' | 'txt';
|
||||
type DesiredRecord = {
|
||||
type: ArvanRecordType;
|
||||
name: string;
|
||||
value: Record<string, unknown>;
|
||||
/** A records use an array of `{ ip }`; other types use a single object. */
|
||||
value: Record<string, unknown> | Array<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
type DnsMutationResult = {
|
||||
@@ -34,7 +35,7 @@ type ArvanDnsRecord = {
|
||||
name: string;
|
||||
ttl?: number;
|
||||
cloud?: boolean;
|
||||
value?: Record<string, unknown> | null;
|
||||
value?: Record<string, unknown> | Array<Record<string, unknown>> | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -110,7 +111,8 @@ export class ArvanDnsService {
|
||||
await this.upsertTyped(host, auth, existing, {
|
||||
type: 'a',
|
||||
name: 'mail',
|
||||
value: { ip: MAIL_IPV4 },
|
||||
// Arvan CDN 4.0 requires A `value` as an array of IP objects.
|
||||
value: [{ ip: MAIL_IPV4 }],
|
||||
}, created, updated, skipped);
|
||||
|
||||
await this.replaceExtraMx(host, auth, existing, removed);
|
||||
@@ -447,7 +449,7 @@ export class ArvanDnsService {
|
||||
const mx = existing.filter((item) => item.type === 'mx');
|
||||
const keep = mx.find((item) => {
|
||||
const name = this.relativeName(item.name, host);
|
||||
return name === '@' && this.fqdn(this.mxHost(item.value ?? {})) === MAIL_HOSTNAME;
|
||||
return name === '@' && this.fqdn(this.mxHost(this.recordValueObject(item.value))) === MAIL_HOSTNAME;
|
||||
});
|
||||
for (const item of mx) {
|
||||
if (keep && item.id === keep.id) continue;
|
||||
@@ -462,7 +464,7 @@ export class ArvanDnsService {
|
||||
);
|
||||
}
|
||||
const name = this.relativeName(item.name, host);
|
||||
removed.push(`MX ${name} ${this.mxHost(item.value ?? {}) || item.id}`);
|
||||
removed.push(`MX ${name} ${this.mxHost(this.recordValueObject(item.value)) || item.id}`);
|
||||
const index = existing.indexOf(item);
|
||||
if (index >= 0) existing.splice(index, 1);
|
||||
}
|
||||
@@ -498,27 +500,51 @@ export class ArvanDnsService {
|
||||
}
|
||||
|
||||
private sameTarget(desired: DesiredRecord, existing: ArvanDnsRecord): boolean {
|
||||
const current = existing.value ?? {};
|
||||
const current = this.recordValueObject(existing.value);
|
||||
const desiredObj = this.recordValueObject(desired.value);
|
||||
if (desired.type === 'aname') {
|
||||
return this.fqdn(String(current.location ?? '')) === this.fqdn(String(desired.value.location ?? ''));
|
||||
return this.fqdn(String(current.location ?? '')) === this.fqdn(String(desiredObj.location ?? ''));
|
||||
}
|
||||
if (desired.type === 'a') {
|
||||
return String(current.ip ?? '') === String(desired.value.ip ?? '');
|
||||
return this.aIps(existing.value).join(',') === this.aIps(desired.value).join(',');
|
||||
}
|
||||
if (desired.type === 'mx') {
|
||||
return (
|
||||
this.fqdn(this.mxHost(current)) === this.fqdn(String(desired.value.host ?? '')) &&
|
||||
Number(current.priority ?? 0) === Number(desired.value.priority ?? 0)
|
||||
this.fqdn(this.mxHost(current)) === this.fqdn(String(desiredObj.host ?? '')) &&
|
||||
Number(current.priority ?? 0) === Number(desiredObj.priority ?? 0)
|
||||
);
|
||||
}
|
||||
if (desired.type === 'txt') {
|
||||
return this.txtValue(existing).replace(/\s+/g, '') === String(desired.value.text ?? '').replace(/\s+/g, '');
|
||||
return this.txtValue(existing).replace(/\s+/g, '') === String(desiredObj.text ?? '').replace(/\s+/g, '');
|
||||
}
|
||||
return this.fqdn(String(current.host ?? '')) === this.fqdn(String(desired.value.host ?? ''));
|
||||
return this.fqdn(String(current.host ?? '')) === this.fqdn(String(desiredObj.host ?? ''));
|
||||
}
|
||||
|
||||
/** Arvan A records return `value` as `[{ ip }]`; normalize to IP list for compare/send. */
|
||||
private aIps(value: DesiredRecord['value'] | ArvanDnsRecord['value']): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((item) => String(item?.ip ?? '').trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
if (value && typeof value === 'object' && 'ip' in value) {
|
||||
const ip = String(value.ip ?? '').trim();
|
||||
return ip ? [ip] : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private recordValueObject(
|
||||
value: DesiredRecord['value'] | ArvanDnsRecord['value'],
|
||||
): Record<string, unknown> {
|
||||
if (Array.isArray(value)) {
|
||||
return (value[0] as Record<string, unknown> | undefined) ?? {};
|
||||
}
|
||||
return value ?? {};
|
||||
}
|
||||
|
||||
private txtValue(record: ArvanDnsRecord): string {
|
||||
const value = record.value ?? {};
|
||||
const value = this.recordValueObject(record.value);
|
||||
if (typeof value.text === 'string') return value.text;
|
||||
if (value.txt && typeof value.txt === 'object' && 'text' in value.txt) {
|
||||
return String((value.txt as { text?: unknown }).text ?? '');
|
||||
|
||||
Reference in New Issue
Block a user