@aoxborrow/registrar-client 0.9.0 → 0.9.1
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/README.md +29 -0
- package/dist/index.cjs +353 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +342 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dns-records.ts +544 -0
- package/src/index.ts +3 -0
- package/src/registrars/domain101.ts +23 -5
package/README.md
CHANGED
|
@@ -425,6 +425,35 @@ sandbox (Name.com offers no domain deletion/reset there); it is excluded from th
|
|
|
425
425
|
default offline suite. Transfer completion and production behavior need separate
|
|
426
426
|
verification; sandbox transfers are not deterministic.
|
|
427
427
|
|
|
428
|
+
## DNS record syntax
|
|
429
|
+
|
|
430
|
+
Pure functions for checking DNS record syntax and putting records in one
|
|
431
|
+
canonical form, with no network calls, so a UI can validate what someone types
|
|
432
|
+
before it reaches a registrar.
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { findCnameConflicts, prepareEntry, toDnsEntry } from '@aoxborrow/registrar-client';
|
|
436
|
+
|
|
437
|
+
// throws DnsSyntaxError with a message safe to show a person
|
|
438
|
+
prepareEntry(toDnsEntry({ type: 'MX', name: '@', value: 'Mail.Example.net.', priority: 10 }));
|
|
439
|
+
// → { owner: '@', kind: 'MX', ttl: 3600, rdata: '10 mail.example.net' }
|
|
440
|
+
|
|
441
|
+
findCnameConflicts(records.map(r => toDnsEntry(r))); // [{ owner, kind, detail }]
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
- Names are folded to lowercase with one trailing dot removed, and are never
|
|
445
|
+
joined to a zone: `www` and `www.example.com` are different owners, and `@`
|
|
446
|
+
stays `@`. Underscore labels (`_dmarc`) and one leftmost `*` are allowed.
|
|
447
|
+
- Data syntax is checked for A, AAAA, CNAME, NS, MX (including null MX), TXT,
|
|
448
|
+
SRV, CAA and DS. Any other type is trimmed and passed through, so a
|
|
449
|
+
provider-specific type still round-trips.
|
|
450
|
+
- `findCnameConflicts` and `findRepeatedRecords` check a whole set of records.
|
|
451
|
+
- `toDnsEntry` adapts a `DnsRecord` (separate MX/SRV number fields) to the
|
|
452
|
+
entry shape the checks take.
|
|
453
|
+
|
|
454
|
+
These are DNS rules only. Each provider still applies its own limits, such as
|
|
455
|
+
a minimum TTL or the record types it accepts.
|
|
456
|
+
|
|
428
457
|
## Sandbox environments
|
|
429
458
|
|
|
430
459
|
Providers that offer a test environment accept `{ environment: 'sandbox' }` at
|
package/dist/index.cjs
CHANGED
|
@@ -2238,20 +2238,32 @@ function fromRecord(raw) {
|
|
|
2238
2238
|
if (raw.type === "MX") {
|
|
2239
2239
|
const match = /^(\d+)\s+(.+)$/.exec(raw.value);
|
|
2240
2240
|
if (!match) throw invalid("MX record");
|
|
2241
|
-
return { ...common, value: match[2], priority: Number(match[1]) };
|
|
2241
|
+
return { ...common, value: canonicalValue("MX", match[2]), priority: Number(match[1]) };
|
|
2242
2242
|
}
|
|
2243
2243
|
if (raw.type === "SRV") {
|
|
2244
2244
|
const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/.exec(raw.value);
|
|
2245
2245
|
if (!match) throw invalid("SRV record");
|
|
2246
2246
|
return {
|
|
2247
2247
|
...common,
|
|
2248
|
-
value: match[4],
|
|
2248
|
+
value: canonicalValue("SRV", match[4]),
|
|
2249
2249
|
priority: Number(match[1]),
|
|
2250
2250
|
weight: Number(match[2]),
|
|
2251
2251
|
port: Number(match[3])
|
|
2252
2252
|
};
|
|
2253
2253
|
}
|
|
2254
|
-
return { ...common, value: raw.value };
|
|
2254
|
+
return { ...common, value: canonicalValue(raw.type, raw.value) };
|
|
2255
|
+
}
|
|
2256
|
+
function canonicalValue(type, value) {
|
|
2257
|
+
const kind = type.toUpperCase();
|
|
2258
|
+
if (kind === "TXT") return unquoteTxt(value);
|
|
2259
|
+
if (["CNAME", "NS", "MX", "SRV"].includes(kind))
|
|
2260
|
+
return value === "." ? "." : value.replace(/\.$/, "");
|
|
2261
|
+
return value;
|
|
2262
|
+
}
|
|
2263
|
+
function unquoteTxt(value) {
|
|
2264
|
+
const chunk = /"((?:[^"\\]|\\.)*)"/g;
|
|
2265
|
+
if (!/^\s*"(?:[^"\\]|\\.)*"(?:\s+"(?:[^"\\]|\\.)*")*\s*$/.test(value)) return value;
|
|
2266
|
+
return [...value.matchAll(chunk)].map((m) => m[1].replace(/\\(.)/g, "$1")).join("");
|
|
2255
2267
|
}
|
|
2256
2268
|
function recordSpec(record, domain, strict = true) {
|
|
2257
2269
|
const type = record.type.toUpperCase();
|
|
@@ -2272,7 +2284,7 @@ function recordSpec(record, domain, strict = true) {
|
|
|
2272
2284
|
if (!Number.isInteger(record.priority) || record.priority < 0 || record.priority > 65535)
|
|
2273
2285
|
reject("MX/SRV priority is required");
|
|
2274
2286
|
result.priority = record.priority;
|
|
2275
|
-
result.target = record.value;
|
|
2287
|
+
result.target = canonicalValue(type, record.value);
|
|
2276
2288
|
if (type === "SRV") {
|
|
2277
2289
|
if (![record.weight, record.port].every((n) => Number.isInteger(n) && n >= 0 && n <= 65535))
|
|
2278
2290
|
reject("SRV weight and port are required");
|
|
@@ -2289,7 +2301,7 @@ function recordSpec(record, domain, strict = true) {
|
|
|
2289
2301
|
result.tag = match[2];
|
|
2290
2302
|
result.value = match[3];
|
|
2291
2303
|
}
|
|
2292
|
-
} else result.value = record.value;
|
|
2304
|
+
} else result.value = canonicalValue(type, record.value);
|
|
2293
2305
|
return result;
|
|
2294
2306
|
}
|
|
2295
2307
|
function recordSignature(record) {
|
|
@@ -8006,6 +8018,330 @@ function createRegistrar(name, credentials, options) {
|
|
|
8006
8018
|
return new RegistrarClass(credentials, options);
|
|
8007
8019
|
}
|
|
8008
8020
|
|
|
8021
|
+
// src/dns-records.ts
|
|
8022
|
+
var DnsSyntaxError = class extends Error {
|
|
8023
|
+
constructor(message) {
|
|
8024
|
+
super(message);
|
|
8025
|
+
this.name = "DnsSyntaxError";
|
|
8026
|
+
}
|
|
8027
|
+
};
|
|
8028
|
+
var NAME_LIMIT = 253;
|
|
8029
|
+
var LABEL_LIMIT = 63;
|
|
8030
|
+
var TTL_LIMIT = 2147483647;
|
|
8031
|
+
var TEXT_OCTETS = 255;
|
|
8032
|
+
var TEXT_TOTAL_OCTETS = 65535 - Math.ceil(65535 / (TEXT_OCTETS + 1));
|
|
8033
|
+
var UINT16_MAX = 65535;
|
|
8034
|
+
var DIGEST_HEX_LENGTH = {
|
|
8035
|
+
1: 40,
|
|
8036
|
+
2: 64,
|
|
8037
|
+
3: 64,
|
|
8038
|
+
4: 96
|
|
8039
|
+
};
|
|
8040
|
+
var KINDS_ALLOWED_BESIDE_CNAME = /* @__PURE__ */ new Set(["CNAME", "RRSIG", "NSEC", "NSEC3"]);
|
|
8041
|
+
function mapAsciiLabel(label) {
|
|
8042
|
+
const folded = label.toLowerCase();
|
|
8043
|
+
if (folded.length < 1 || folded.length > LABEL_LIMIT) return null;
|
|
8044
|
+
if (!/^[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?$/.test(folded)) return null;
|
|
8045
|
+
return folded;
|
|
8046
|
+
}
|
|
8047
|
+
var labelMapper = mapAsciiLabel;
|
|
8048
|
+
function useLabelMapper(mapper) {
|
|
8049
|
+
labelMapper = mapper;
|
|
8050
|
+
}
|
|
8051
|
+
function resetLabelMapper() {
|
|
8052
|
+
labelMapper = mapAsciiLabel;
|
|
8053
|
+
}
|
|
8054
|
+
function prepareName(input, role = "owner") {
|
|
8055
|
+
const trimmed = input.trim();
|
|
8056
|
+
if (!trimmed) throw new DnsSyntaxError("Name is empty");
|
|
8057
|
+
const bare = trimmed.endsWith(".") ? trimmed.slice(0, -1) : trimmed;
|
|
8058
|
+
if (!bare) throw new DnsSyntaxError("Name is empty");
|
|
8059
|
+
if (bare === "@") {
|
|
8060
|
+
if (role === "target") throw new DnsSyntaxError("Origin token @ cannot be a record target");
|
|
8061
|
+
return "@";
|
|
8062
|
+
}
|
|
8063
|
+
const labels = bare.split(".");
|
|
8064
|
+
if (labels.some((label) => label.length === 0)) {
|
|
8065
|
+
throw new DnsSyntaxError("Name contains an empty label");
|
|
8066
|
+
}
|
|
8067
|
+
const starLabels = labels.filter((label) => label.includes("*"));
|
|
8068
|
+
if (starLabels.length > 0) {
|
|
8069
|
+
if (role !== "owner") {
|
|
8070
|
+
throw new DnsSyntaxError("A wildcard is only valid as an owner name");
|
|
8071
|
+
}
|
|
8072
|
+
const starCount = starLabels.reduce(
|
|
8073
|
+
(count, label) => count + (label.match(/\*/g)?.length ?? 0),
|
|
8074
|
+
0
|
|
8075
|
+
);
|
|
8076
|
+
if (starCount > 1) throw new DnsSyntaxError("A name can contain only one wildcard");
|
|
8077
|
+
if (labels[0] !== "*") throw new DnsSyntaxError("A wildcard must be its own leftmost label");
|
|
8078
|
+
}
|
|
8079
|
+
const mapped = labels.map((label) => {
|
|
8080
|
+
if (label === "*") return "*";
|
|
8081
|
+
const ascii = labelMapper(label);
|
|
8082
|
+
if (!ascii) throw new DnsSyntaxError(`Label is not a valid DNS label: ${label}`);
|
|
8083
|
+
return ascii.toLowerCase();
|
|
8084
|
+
});
|
|
8085
|
+
const name = mapped.join(".");
|
|
8086
|
+
if (name.length > NAME_LIMIT) throw new DnsSyntaxError("Name is longer than 253 characters");
|
|
8087
|
+
return name;
|
|
8088
|
+
}
|
|
8089
|
+
function isIpv4(text2) {
|
|
8090
|
+
const octets = text2.split(".");
|
|
8091
|
+
if (octets.length !== 4) return false;
|
|
8092
|
+
return octets.every((octet) => {
|
|
8093
|
+
if (!/^\d{1,3}$/.test(octet)) return false;
|
|
8094
|
+
const value = Number(octet);
|
|
8095
|
+
return value >= 0 && value <= 255 && String(value) === octet;
|
|
8096
|
+
});
|
|
8097
|
+
}
|
|
8098
|
+
function isIpv6(text2) {
|
|
8099
|
+
let body = text2;
|
|
8100
|
+
const embedded = text2.match(/^(.+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
8101
|
+
if (embedded) {
|
|
8102
|
+
if (!isIpv4(embedded[2])) return false;
|
|
8103
|
+
body = `${embedded[1]}:0:0`;
|
|
8104
|
+
}
|
|
8105
|
+
const compressedAt = body.indexOf("::");
|
|
8106
|
+
if (compressedAt !== -1) {
|
|
8107
|
+
if (body.indexOf("::", compressedAt + 1) !== -1) return false;
|
|
8108
|
+
const groupsOf = (side) => side === "" ? [] : side.split(":");
|
|
8109
|
+
const head = groupsOf(body.slice(0, compressedAt));
|
|
8110
|
+
const tail = groupsOf(body.slice(compressedAt + 2));
|
|
8111
|
+
if (head.length + tail.length > 7) return false;
|
|
8112
|
+
return [...head, ...tail].every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
8113
|
+
}
|
|
8114
|
+
const groups = body.split(":");
|
|
8115
|
+
return groups.length === 8 && groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
8116
|
+
}
|
|
8117
|
+
function utf8OctetCount(text2) {
|
|
8118
|
+
let count = 0;
|
|
8119
|
+
for (const char of text2) {
|
|
8120
|
+
const code = char.codePointAt(0) ?? 0;
|
|
8121
|
+
if (code <= 127) count += 1;
|
|
8122
|
+
else if (code <= 2047) count += 2;
|
|
8123
|
+
else if (code <= 65535) count += 3;
|
|
8124
|
+
else count += 4;
|
|
8125
|
+
}
|
|
8126
|
+
return count;
|
|
8127
|
+
}
|
|
8128
|
+
function decimalInRange(token, limit) {
|
|
8129
|
+
if (!/^(0|[1-9][0-9]*)$/.test(token)) return null;
|
|
8130
|
+
const value = Number(token);
|
|
8131
|
+
if (value > limit) return null;
|
|
8132
|
+
return value;
|
|
8133
|
+
}
|
|
8134
|
+
function prepareIpv4(text2) {
|
|
8135
|
+
if (!isIpv4(text2)) throw new DnsSyntaxError("IPv4 address is invalid");
|
|
8136
|
+
return text2;
|
|
8137
|
+
}
|
|
8138
|
+
function prepareIpv6(text2) {
|
|
8139
|
+
if (!isIpv6(text2)) throw new DnsSyntaxError("IPv6 address is invalid");
|
|
8140
|
+
return text2.toLowerCase();
|
|
8141
|
+
}
|
|
8142
|
+
function prepareHostTarget(text2, owner, rejectSelf) {
|
|
8143
|
+
const withoutDot = text2.endsWith(".") ? text2.slice(0, -1) : text2;
|
|
8144
|
+
if (isIpv4(withoutDot) || isIpv6(withoutDot)) {
|
|
8145
|
+
throw new DnsSyntaxError("Target must be a domain name, not an address");
|
|
8146
|
+
}
|
|
8147
|
+
const target = prepareName(text2, "target");
|
|
8148
|
+
if (rejectSelf && owner !== void 0 && prepareName(owner, "owner") === target) {
|
|
8149
|
+
throw new DnsSyntaxError("CNAME target is the same name as its owner");
|
|
8150
|
+
}
|
|
8151
|
+
return target;
|
|
8152
|
+
}
|
|
8153
|
+
function prepareMailExchange(text2) {
|
|
8154
|
+
const parts = text2.split(/\s+/);
|
|
8155
|
+
if (parts.length !== 2) {
|
|
8156
|
+
throw new DnsSyntaxError('MX data must be "<priority> <mail host>"');
|
|
8157
|
+
}
|
|
8158
|
+
const priority = decimalInRange(parts[0], UINT16_MAX);
|
|
8159
|
+
if (priority === null) throw new DnsSyntaxError("MX priority must be an integer from 0 to 65535");
|
|
8160
|
+
return `${priority} ${prepareServiceHost(parts[1])}`;
|
|
8161
|
+
}
|
|
8162
|
+
function prepareServiceHost(text2) {
|
|
8163
|
+
return text2 === "." ? "." : prepareHostTarget(text2, void 0, false);
|
|
8164
|
+
}
|
|
8165
|
+
function prepareService(text2) {
|
|
8166
|
+
const parts = text2.split(/\s+/);
|
|
8167
|
+
if (parts.length !== 4) {
|
|
8168
|
+
throw new DnsSyntaxError('SRV data must be "<priority> <weight> <port> <target>"');
|
|
8169
|
+
}
|
|
8170
|
+
const [priority, weight, port] = parts.slice(0, 3).map((part) => decimalInRange(part, UINT16_MAX));
|
|
8171
|
+
if (priority === null)
|
|
8172
|
+
throw new DnsSyntaxError("SRV priority must be an integer from 0 to 65535");
|
|
8173
|
+
if (weight === null) throw new DnsSyntaxError("SRV weight must be an integer from 0 to 65535");
|
|
8174
|
+
if (port === null) throw new DnsSyntaxError("SRV port must be an integer from 0 to 65535");
|
|
8175
|
+
return `${priority} ${weight} ${port} ${prepareServiceHost(parts[3])}`;
|
|
8176
|
+
}
|
|
8177
|
+
function prepareAuthorization(text2) {
|
|
8178
|
+
const match = /^(\S+)\s+(\S+)\s+([\s\S]+)$/.exec(text2);
|
|
8179
|
+
if (!match) throw new DnsSyntaxError('CAA data must be "<flags> <tag> <value>"');
|
|
8180
|
+
const flags = decimalInRange(match[1], 255);
|
|
8181
|
+
if (flags === null) throw new DnsSyntaxError("CAA flags must be an integer from 0 to 255");
|
|
8182
|
+
if (!/^[a-z0-9]{1,15}$/i.test(match[2])) {
|
|
8183
|
+
throw new DnsSyntaxError("CAA tag must be 1 to 15 letters or digits");
|
|
8184
|
+
}
|
|
8185
|
+
const quoted = /^"((?:[^"\\]|\\.)*)"$/.exec(match[3]);
|
|
8186
|
+
const value = quoted ? quoted[1] : match[3];
|
|
8187
|
+
if (!quoted && value.includes('"')) {
|
|
8188
|
+
throw new DnsSyntaxError("CAA value must be one quoted or unquoted string");
|
|
8189
|
+
}
|
|
8190
|
+
return `${flags} ${match[2].toLowerCase()} "${value}"`;
|
|
8191
|
+
}
|
|
8192
|
+
function prepareText(text2) {
|
|
8193
|
+
if (utf8OctetCount(text2) > TEXT_TOTAL_OCTETS) {
|
|
8194
|
+
throw new DnsSyntaxError(`TXT data is longer than ${TEXT_TOTAL_OCTETS} octets`);
|
|
8195
|
+
}
|
|
8196
|
+
return text2;
|
|
8197
|
+
}
|
|
8198
|
+
function readSignerFields(text2) {
|
|
8199
|
+
const parts = text2.trim().split(/\s+/);
|
|
8200
|
+
if (parts.length !== 4)
|
|
8201
|
+
throw new DnsSyntaxError('DS data must be "<key tag> <algorithm> <digest type> <digest>"');
|
|
8202
|
+
const keyTag = decimalInRange(parts[0], UINT16_MAX);
|
|
8203
|
+
const algorithm = decimalInRange(parts[1], 255);
|
|
8204
|
+
const digestType = decimalInRange(parts[2], 255);
|
|
8205
|
+
if (keyTag === null || algorithm === null || digestType === null || !parts[3]) {
|
|
8206
|
+
throw new DnsSyntaxError('DS data must be "<key tag> <algorithm> <digest type> <digest>"');
|
|
8207
|
+
}
|
|
8208
|
+
const digest = parts[3].toLowerCase();
|
|
8209
|
+
if (!/^[0-9a-f]+$/.test(digest)) throw new DnsSyntaxError("DS digest must be hexadecimal");
|
|
8210
|
+
const expectedLength = DIGEST_HEX_LENGTH[digestType];
|
|
8211
|
+
if (expectedLength !== void 0 && digest.length !== expectedLength) {
|
|
8212
|
+
throw new DnsSyntaxError(
|
|
8213
|
+
`DS digest must be ${expectedLength} hex characters for digest type ${digestType}`
|
|
8214
|
+
);
|
|
8215
|
+
}
|
|
8216
|
+
return { keyTag, algorithm, digestType, digest };
|
|
8217
|
+
}
|
|
8218
|
+
function readDelegationSigner(rdata) {
|
|
8219
|
+
return readSignerFields(rdata);
|
|
8220
|
+
}
|
|
8221
|
+
function prepareSignerText(text2) {
|
|
8222
|
+
const signer = readSignerFields(text2);
|
|
8223
|
+
return `${signer.keyTag} ${signer.algorithm} ${signer.digestType} ${signer.digest}`;
|
|
8224
|
+
}
|
|
8225
|
+
function prepareRdata(kind, rdata, owner) {
|
|
8226
|
+
const token = kind.trim().toUpperCase();
|
|
8227
|
+
const text2 = rdata.trim();
|
|
8228
|
+
if (!text2) throw new DnsSyntaxError("Record data is empty");
|
|
8229
|
+
switch (token) {
|
|
8230
|
+
case "A":
|
|
8231
|
+
return prepareIpv4(text2);
|
|
8232
|
+
case "AAAA":
|
|
8233
|
+
return prepareIpv6(text2);
|
|
8234
|
+
case "CNAME":
|
|
8235
|
+
return prepareHostTarget(text2, owner, true);
|
|
8236
|
+
case "NS":
|
|
8237
|
+
return prepareHostTarget(text2, void 0, false);
|
|
8238
|
+
case "MX":
|
|
8239
|
+
return prepareMailExchange(text2);
|
|
8240
|
+
case "TXT":
|
|
8241
|
+
return prepareText(text2);
|
|
8242
|
+
case "SRV":
|
|
8243
|
+
return prepareService(text2);
|
|
8244
|
+
case "CAA":
|
|
8245
|
+
return prepareAuthorization(text2);
|
|
8246
|
+
case "DS":
|
|
8247
|
+
return prepareSignerText(text2);
|
|
8248
|
+
default:
|
|
8249
|
+
return text2;
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
function prepareEntry(entry) {
|
|
8253
|
+
if (!Number.isInteger(entry.ttl) || entry.ttl < 0 || entry.ttl > TTL_LIMIT) {
|
|
8254
|
+
throw new DnsSyntaxError("TTL must be an integer from 0 to 2147483647");
|
|
8255
|
+
}
|
|
8256
|
+
const kind = entry.kind.trim().toUpperCase();
|
|
8257
|
+
if (!kind) throw new DnsSyntaxError("Record type is empty");
|
|
8258
|
+
return {
|
|
8259
|
+
owner: prepareName(entry.owner, "owner"),
|
|
8260
|
+
kind,
|
|
8261
|
+
ttl: entry.ttl,
|
|
8262
|
+
rdata: prepareRdata(kind, entry.rdata, entry.owner)
|
|
8263
|
+
};
|
|
8264
|
+
}
|
|
8265
|
+
function syntaxClash(entry, error) {
|
|
8266
|
+
const detail = error instanceof Error ? error.message : "Invalid record";
|
|
8267
|
+
return { owner: entry.owner, kind: entry.kind, detail };
|
|
8268
|
+
}
|
|
8269
|
+
function findCnameConflicts(entries) {
|
|
8270
|
+
const clashes = [];
|
|
8271
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
8272
|
+
for (const entry of entries) {
|
|
8273
|
+
let owner;
|
|
8274
|
+
let kind;
|
|
8275
|
+
try {
|
|
8276
|
+
owner = prepareName(entry.owner, "owner");
|
|
8277
|
+
kind = entry.kind.trim().toUpperCase();
|
|
8278
|
+
if (!kind) throw new DnsSyntaxError("Record type is empty");
|
|
8279
|
+
} catch (error) {
|
|
8280
|
+
clashes.push(syntaxClash(entry, error));
|
|
8281
|
+
continue;
|
|
8282
|
+
}
|
|
8283
|
+
const group = byOwner.get(owner) ?? [];
|
|
8284
|
+
group.push({ ...entry, owner, kind });
|
|
8285
|
+
byOwner.set(owner, group);
|
|
8286
|
+
}
|
|
8287
|
+
for (const [owner, group] of byOwner) {
|
|
8288
|
+
const aliasCount = group.filter((entry) => entry.kind === "CNAME").length;
|
|
8289
|
+
if (aliasCount === 0) continue;
|
|
8290
|
+
if (aliasCount > 1) {
|
|
8291
|
+
clashes.push({
|
|
8292
|
+
owner,
|
|
8293
|
+
kind: "CNAME",
|
|
8294
|
+
detail: "A name can have only one CNAME target"
|
|
8295
|
+
});
|
|
8296
|
+
}
|
|
8297
|
+
const others = [...new Set(group.map((entry) => entry.kind))].filter(
|
|
8298
|
+
(kind) => !KINDS_ALLOWED_BESIDE_CNAME.has(kind)
|
|
8299
|
+
);
|
|
8300
|
+
if (others.length > 0) {
|
|
8301
|
+
clashes.push({
|
|
8302
|
+
owner,
|
|
8303
|
+
kind: "CNAME",
|
|
8304
|
+
detail: `A CNAME cannot share a name with ${others.join(", ")}`
|
|
8305
|
+
});
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
return clashes;
|
|
8309
|
+
}
|
|
8310
|
+
function findRepeatedRecords(entries) {
|
|
8311
|
+
const clashes = [];
|
|
8312
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8313
|
+
for (const entry of entries) {
|
|
8314
|
+
let prepared;
|
|
8315
|
+
try {
|
|
8316
|
+
prepared = prepareEntry(entry);
|
|
8317
|
+
} catch (error) {
|
|
8318
|
+
clashes.push(syntaxClash(entry, error));
|
|
8319
|
+
continue;
|
|
8320
|
+
}
|
|
8321
|
+
if (prepared.kind === "CNAME") continue;
|
|
8322
|
+
const key = `${prepared.owner}\0${prepared.kind}\0${prepared.rdata}`;
|
|
8323
|
+
if (seen.has(key)) {
|
|
8324
|
+
clashes.push({
|
|
8325
|
+
owner: prepared.owner,
|
|
8326
|
+
kind: prepared.kind,
|
|
8327
|
+
detail: `Repeated ${prepared.kind} data at this name`
|
|
8328
|
+
});
|
|
8329
|
+
}
|
|
8330
|
+
seen.add(key);
|
|
8331
|
+
}
|
|
8332
|
+
return clashes;
|
|
8333
|
+
}
|
|
8334
|
+
function toDnsEntry(record, defaultTtl = 3600) {
|
|
8335
|
+
const kind = record.type.trim().toUpperCase();
|
|
8336
|
+
const numbers = kind === "MX" ? [record.priority] : kind === "SRV" ? [record.priority, record.weight, record.port] : [];
|
|
8337
|
+
return {
|
|
8338
|
+
owner: record.name,
|
|
8339
|
+
kind,
|
|
8340
|
+
ttl: record.ttl ?? defaultTtl,
|
|
8341
|
+
rdata: [...numbers.map((n) => n === void 0 ? "" : String(n)), record.value.trim()].join(" ")
|
|
8342
|
+
};
|
|
8343
|
+
}
|
|
8344
|
+
|
|
8009
8345
|
exports.ALL_FEATURES = ALL_FEATURES;
|
|
8010
8346
|
exports.AbortError = AbortError;
|
|
8011
8347
|
exports.AuthenticationError = AuthenticationError;
|
|
@@ -8017,6 +8353,7 @@ exports.ConfigurationError = ConfigurationError;
|
|
|
8017
8353
|
exports.ConnectionError = ConnectionError;
|
|
8018
8354
|
exports.ConsentRequiredError = ConsentRequiredError;
|
|
8019
8355
|
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
8356
|
+
exports.DnsSyntaxError = DnsSyntaxError;
|
|
8020
8357
|
exports.Domain101Registrar = Domain101Registrar;
|
|
8021
8358
|
exports.DynadotRegistrar = DynadotRegistrar;
|
|
8022
8359
|
exports.EXTENDED_FEATURES = EXTENDED_FEATURES;
|
|
@@ -8050,19 +8387,30 @@ exports.createDomain = createDomain;
|
|
|
8050
8387
|
exports.createRegistrar = createRegistrar;
|
|
8051
8388
|
exports.ensureArray = ensureArray;
|
|
8052
8389
|
exports.filterDomains = filterDomains;
|
|
8390
|
+
exports.findCnameConflicts = findCnameConflicts;
|
|
8391
|
+
exports.findRepeatedRecords = findRepeatedRecords;
|
|
8053
8392
|
exports.isCoreFeature = isCoreFeature;
|
|
8393
|
+
exports.isIpv4 = isIpv4;
|
|
8394
|
+
exports.isIpv6 = isIpv6;
|
|
8054
8395
|
exports.listPortfolio = listPortfolio;
|
|
8055
8396
|
exports.markNotSent = markNotSent;
|
|
8056
8397
|
exports.normalizeDomain = normalizeDomain;
|
|
8057
8398
|
exports.normalizeNameservers = normalizeNameservers;
|
|
8058
8399
|
exports.parseDate = parseDate;
|
|
8059
8400
|
exports.parseXml = parseXml;
|
|
8401
|
+
exports.prepareEntry = prepareEntry;
|
|
8402
|
+
exports.prepareName = prepareName;
|
|
8403
|
+
exports.prepareRdata = prepareRdata;
|
|
8404
|
+
exports.readDelegationSigner = readDelegationSigner;
|
|
8060
8405
|
exports.registrars = registrars;
|
|
8061
8406
|
exports.requireConsent = requireConsent;
|
|
8407
|
+
exports.resetLabelMapper = resetLabelMapper;
|
|
8062
8408
|
exports.selectBaseUrl = selectBaseUrl;
|
|
8063
8409
|
exports.settableForwards = settableForwards;
|
|
8064
8410
|
exports.sleep = sleep;
|
|
8411
|
+
exports.toDnsEntry = toDnsEntry;
|
|
8065
8412
|
exports.toRegistrarError = toRegistrarError;
|
|
8413
|
+
exports.useLabelMapper = useLabelMapper;
|
|
8066
8414
|
exports.wasNotSent = wasNotSent;
|
|
8067
8415
|
//# sourceMappingURL=index.cjs.map
|
|
8068
8416
|
//# sourceMappingURL=index.cjs.map
|