@aoxborrow/registrar-client 0.8.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 +78 -43
- package/dist/index.cjs +903 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +891 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dns-records.ts +544 -0
- package/src/index.ts +4 -0
- package/src/registrars/domain101.ts +727 -0
- package/src/registrars/index.ts +12 -10
- package/src/types.ts +3 -0
package/dist/index.js
CHANGED
|
@@ -1076,8 +1076,8 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1076
1076
|
message: `${domainName} is not a zone in this account; DNS is managed via the Zones API`
|
|
1077
1077
|
};
|
|
1078
1078
|
}
|
|
1079
|
-
const
|
|
1080
|
-
const list = await this.http.request({ path:
|
|
1079
|
+
const dnsPath2 = `/zones/${encodeURIComponent(zone.id)}/dns_records`;
|
|
1080
|
+
const list = await this.http.request({ path: dnsPath2, ...opts });
|
|
1081
1081
|
if (!list.success) {
|
|
1082
1082
|
return {
|
|
1083
1083
|
success: false,
|
|
@@ -1088,7 +1088,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1088
1088
|
if (!rec.id) continue;
|
|
1089
1089
|
const del = await this.http.request({
|
|
1090
1090
|
method: "DELETE",
|
|
1091
|
-
path: `${
|
|
1091
|
+
path: `${dnsPath2}/${encodeURIComponent(rec.id)}`,
|
|
1092
1092
|
...opts
|
|
1093
1093
|
});
|
|
1094
1094
|
if (!del.success) {
|
|
@@ -1104,7 +1104,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1104
1104
|
if (type === "NS" && name === zone.name) continue;
|
|
1105
1105
|
const create = await this.http.request({
|
|
1106
1106
|
method: "POST",
|
|
1107
|
-
path:
|
|
1107
|
+
path: dnsPath2,
|
|
1108
1108
|
body: toCfDnsBody(record, type, name),
|
|
1109
1109
|
...opts
|
|
1110
1110
|
});
|
|
@@ -1773,6 +1773,539 @@ function toContact(c) {
|
|
|
1773
1773
|
return contact;
|
|
1774
1774
|
}
|
|
1775
1775
|
|
|
1776
|
+
// src/registrars/domain101.ts
|
|
1777
|
+
var Domain101HttpClient = class extends HttpClient {
|
|
1778
|
+
async request(req) {
|
|
1779
|
+
try {
|
|
1780
|
+
return await super.request(req);
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
if (error instanceof AuthorizationError) {
|
|
1783
|
+
const scope = requiredScope(req);
|
|
1784
|
+
if (!scope || error.providerCode !== "INSUFFICIENT_PERMISSIONS") throw error;
|
|
1785
|
+
const scoped = new AuthorizationError(
|
|
1786
|
+
`101domain: this API key does not have the ${scope} scope. Create a key with ${scope} to ${SCOPE_PURPOSE[scope]}.`
|
|
1787
|
+
);
|
|
1788
|
+
scoped.providerCode = error.providerCode;
|
|
1789
|
+
throw scoped;
|
|
1790
|
+
}
|
|
1791
|
+
if (error instanceof OutcomeUnknownError)
|
|
1792
|
+
throw new OutcomeUnknownError(
|
|
1793
|
+
"101domain: the registrar did not confirm this change. Re-read the domain before trying again.",
|
|
1794
|
+
{ feature: error.feature }
|
|
1795
|
+
);
|
|
1796
|
+
if (error instanceof ParsingError) throw new ParsingError("101domain: invalid JSON response");
|
|
1797
|
+
if (error instanceof ConnectionError)
|
|
1798
|
+
throw new ConnectionError("101domain: network request failed", { notSent: error.notSent });
|
|
1799
|
+
throw error;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
async toStatusError(response, url) {
|
|
1803
|
+
const detail = await errorDetail(response);
|
|
1804
|
+
const error = await super.toStatusError(
|
|
1805
|
+
new Response(null, { status: response.status, headers: response.headers }),
|
|
1806
|
+
url
|
|
1807
|
+
);
|
|
1808
|
+
if (detail) {
|
|
1809
|
+
error.message = `101domain: ${detail.message}`;
|
|
1810
|
+
error.providerCode = detail.code;
|
|
1811
|
+
} else if (response.status === 403) {
|
|
1812
|
+
error.message = "101domain: the request was refused before reaching the API (403 without an API error)";
|
|
1813
|
+
}
|
|
1814
|
+
return error;
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
var SCOPE_PURPOSE = {
|
|
1818
|
+
domains_read: "read the domain portfolio",
|
|
1819
|
+
domains_write: "change URL forwarding",
|
|
1820
|
+
dns_read: "read DNS records and nameservers",
|
|
1821
|
+
dns_write: "change DNS records and nameservers"
|
|
1822
|
+
};
|
|
1823
|
+
function requiredScope(req) {
|
|
1824
|
+
const write = !REST_SAFE_METHODS.includes((req.method ?? "GET").toUpperCase());
|
|
1825
|
+
if (req.path.startsWith("/dns/")) return write ? "dns_write" : "dns_read";
|
|
1826
|
+
if (req.path === "/domains" || /^\/domains\/[^/]+(?:\/forwarding)?$/.test(req.path))
|
|
1827
|
+
return write ? "domains_write" : "domains_read";
|
|
1828
|
+
return void 0;
|
|
1829
|
+
}
|
|
1830
|
+
async function errorDetail(response) {
|
|
1831
|
+
try {
|
|
1832
|
+
const body = await response.json();
|
|
1833
|
+
if (typeof body?.code !== "string" || typeof body.message !== "string" || !body.message)
|
|
1834
|
+
return null;
|
|
1835
|
+
return { code: body.code, message: body.message.replace(/\s+/g, " ").slice(0, 200) };
|
|
1836
|
+
} catch {
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
var Domain101Registrar = class extends BaseRegistrar {
|
|
1841
|
+
name = "101domain";
|
|
1842
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
1843
|
+
static displayName = "101domain";
|
|
1844
|
+
static website = "101domain.com";
|
|
1845
|
+
static supportsSandbox = false;
|
|
1846
|
+
static configFields = [
|
|
1847
|
+
{ name: "apiKey", label: "API key", type: "password", required: true }
|
|
1848
|
+
];
|
|
1849
|
+
static helpText = "Create a Bearer API key under My Account > Developer Tools - API & MCP. Use domains_read and dns_read for reads; dns_write for DNS/nameservers and domains_write for forwarding. Only a primary account with 2FA or SSO can create keys. Keys expire after at most one year. Registration, renewal, auto-renew, privacy, lock, contacts and transfers are not available through this API.";
|
|
1850
|
+
static extendedFeatures = [
|
|
1851
|
+
Feature.GetDomainForwarding,
|
|
1852
|
+
Feature.SetDomainForwarding
|
|
1853
|
+
];
|
|
1854
|
+
constructor(credentials, options) {
|
|
1855
|
+
const apiKey = credentials.apiKey?.trim() ?? "";
|
|
1856
|
+
if (/[\r\n]/.test(apiKey)) throw new ConfigurationError("101domain: invalid API key format");
|
|
1857
|
+
const config = {
|
|
1858
|
+
baseUrl: selectBaseUrl("101domain", options?.environment, {
|
|
1859
|
+
production: "https://api.101domain.com/v1"
|
|
1860
|
+
}),
|
|
1861
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1862
|
+
};
|
|
1863
|
+
super(credentials, config, options);
|
|
1864
|
+
this.http = new Domain101HttpClient({
|
|
1865
|
+
...config,
|
|
1866
|
+
safeMethods: REST_SAFE_METHODS,
|
|
1867
|
+
options: this.options
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
async testConnection(opts) {
|
|
1871
|
+
try {
|
|
1872
|
+
await this.request({
|
|
1873
|
+
path: "/domains",
|
|
1874
|
+
query: { page: 1, per_page: 1 },
|
|
1875
|
+
...opts
|
|
1876
|
+
});
|
|
1877
|
+
return { success: true, message: "Connection successful" };
|
|
1878
|
+
} catch (error) {
|
|
1879
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
async listDomains(opts) {
|
|
1883
|
+
const { search, detailed: _detailed, ...requestOpts } = opts ?? {};
|
|
1884
|
+
const result = [];
|
|
1885
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1886
|
+
for (let page = 1; ; page++) {
|
|
1887
|
+
const response = await this.request({
|
|
1888
|
+
path: "/domains",
|
|
1889
|
+
query: { page, per_page: 50 },
|
|
1890
|
+
...requestOpts
|
|
1891
|
+
});
|
|
1892
|
+
if (!Array.isArray(response.data)) throw invalid("domain list");
|
|
1893
|
+
const pagination = response.meta?.pagination;
|
|
1894
|
+
if (!pagination || pagination.current_page !== page || !Number.isInteger(pagination.total_pages) || pagination.total_pages < 0 || pagination.total_pages > 1e5 || !Number.isInteger(pagination.total) || pagination.total < 0)
|
|
1895
|
+
throw invalid("pagination");
|
|
1896
|
+
for (const raw of response.data) {
|
|
1897
|
+
const domain = toDomain(raw);
|
|
1898
|
+
if (seen.has(domain.domainName)) throw invalid("duplicate domain across pages");
|
|
1899
|
+
seen.add(domain.domainName);
|
|
1900
|
+
result.push(domain);
|
|
1901
|
+
}
|
|
1902
|
+
if (page >= pagination.total_pages) {
|
|
1903
|
+
if (result.length !== pagination.total) throw invalid("incomplete portfolio");
|
|
1904
|
+
return filterDomains(
|
|
1905
|
+
result.filter((d) => !d.deleted),
|
|
1906
|
+
search
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
if (!response.data.length) throw invalid("empty intermediate page");
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
async getDomain(domainName, opts) {
|
|
1913
|
+
const domain = toDomain(
|
|
1914
|
+
(await this.request({ path: domainPath(domainName), ...opts })).data
|
|
1915
|
+
);
|
|
1916
|
+
if (domain.domainName !== normalizeDomain(domainName)) throw invalid("domain identity");
|
|
1917
|
+
return domain;
|
|
1918
|
+
}
|
|
1919
|
+
async getNameservers(domainName, opts) {
|
|
1920
|
+
const raw = (await this.request({ path: `${dnsPath(domainName)}/nameservers`, ...opts })).data;
|
|
1921
|
+
if (!Array.isArray(raw) || raw.some((n) => typeof n !== "string")) throw invalid("nameservers");
|
|
1922
|
+
return raw.map((n) => n.toLowerCase());
|
|
1923
|
+
}
|
|
1924
|
+
async updateNameservers(domainName, nameservers, opts) {
|
|
1925
|
+
const normalized = nameservers.map((n) => n.trim().toLowerCase());
|
|
1926
|
+
if (normalized.length < 2 || normalized.length > 13 || new Set(normalized).size !== normalized.length || normalized.some((n) => !/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9-]+$/.test(n)))
|
|
1927
|
+
throw new ConfigurationError("101domain: provide 2\u201313 distinct nameserver hostnames");
|
|
1928
|
+
try {
|
|
1929
|
+
const response = await this.request({
|
|
1930
|
+
...opts,
|
|
1931
|
+
method: "PUT",
|
|
1932
|
+
path: `${dnsPath(domainName)}/nameservers`,
|
|
1933
|
+
body: { nameservers: normalized }
|
|
1934
|
+
});
|
|
1935
|
+
if (response.data?.change_status === "pending")
|
|
1936
|
+
return {
|
|
1937
|
+
success: true,
|
|
1938
|
+
pending: true,
|
|
1939
|
+
message: "Nameserver change submitted; pending registry processing. Re-read nameservers to confirm completion."
|
|
1940
|
+
};
|
|
1941
|
+
if (response.data?.change_status === "failed")
|
|
1942
|
+
return { success: false, message: "101domain rejected the nameserver change" };
|
|
1943
|
+
if (response.data?.change_status !== "completed")
|
|
1944
|
+
throw new OutcomeUnknownError(
|
|
1945
|
+
"101domain: invalid nameserver change confirmation. Re-read nameservers before trying again."
|
|
1946
|
+
);
|
|
1947
|
+
return { success: true, message: "Nameservers already configured as requested" };
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
return failed(error);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
async checkAvailability(domainNames, opts) {
|
|
1953
|
+
const names = [...new Set(domainNames.map(normalizeDomain))];
|
|
1954
|
+
const result = [];
|
|
1955
|
+
for (const domains of chunks(names, 50)) {
|
|
1956
|
+
const response = await this.request({
|
|
1957
|
+
...opts,
|
|
1958
|
+
method: "POST",
|
|
1959
|
+
path: "/domains/bulk-search",
|
|
1960
|
+
body: { domains, pricing_term: 1 }
|
|
1961
|
+
});
|
|
1962
|
+
if (!Array.isArray(response.data) || (response.invalid?.length ?? 0) > 0)
|
|
1963
|
+
throw invalid("availability results");
|
|
1964
|
+
const found = /* @__PURE__ */ new Set();
|
|
1965
|
+
for (const raw of response.data) {
|
|
1966
|
+
if (typeof raw.domain_name !== "string" || typeof raw.available !== "boolean")
|
|
1967
|
+
throw invalid("availability result");
|
|
1968
|
+
const domainName = normalizeDomain(raw.domain_name);
|
|
1969
|
+
if (!domains.includes(domainName) || found.has(domainName))
|
|
1970
|
+
throw invalid("availability identity");
|
|
1971
|
+
found.add(domainName);
|
|
1972
|
+
const price2 = oneYear(raw.pricing);
|
|
1973
|
+
result.push({
|
|
1974
|
+
domainName,
|
|
1975
|
+
available: raw.available,
|
|
1976
|
+
premium: price2?.premium,
|
|
1977
|
+
price: amount(price2?.register),
|
|
1978
|
+
renewalPrice: amount(price2?.renew),
|
|
1979
|
+
currency: price2?.currency,
|
|
1980
|
+
period: 1
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
if (found.size !== domains.length) throw invalid("incomplete availability results");
|
|
1984
|
+
}
|
|
1985
|
+
return result;
|
|
1986
|
+
}
|
|
1987
|
+
async getPricing(tldOrDomain, opts) {
|
|
1988
|
+
const name = normalizeDomain(tldOrDomain);
|
|
1989
|
+
if (name.startsWith(".") || !name.includes(".")) {
|
|
1990
|
+
const tld = name.replace(/^\./, "");
|
|
1991
|
+
const response2 = await this.request({
|
|
1992
|
+
path: `/tlds/${encodeURIComponent("." + tld)}`,
|
|
1993
|
+
query: { pricing_terms: "1" },
|
|
1994
|
+
...opts
|
|
1995
|
+
});
|
|
1996
|
+
const price3 = oneYear(response2.data?.pricing);
|
|
1997
|
+
if (!price3) throw new NotFoundError("101domain: no one-year pricing for this TLD");
|
|
1998
|
+
return {
|
|
1999
|
+
tld,
|
|
2000
|
+
currency: price3.currency,
|
|
2001
|
+
registration: amount(price3.register),
|
|
2002
|
+
renewal: amount(price3.renew),
|
|
2003
|
+
transfer: amount(price3.transfer)
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
const response = await this.request({
|
|
2007
|
+
path: "/domains/search",
|
|
2008
|
+
query: { domain_name: name, pricing_terms: "1" },
|
|
2009
|
+
...opts
|
|
2010
|
+
});
|
|
2011
|
+
const raw = response.data;
|
|
2012
|
+
if (!raw || normalizeDomain(raw.domain_name) !== name) throw invalid("pricing identity");
|
|
2013
|
+
const price2 = oneYear(raw.pricing);
|
|
2014
|
+
if (!price2) throw new NotFoundError("101domain: pricing unavailable for this domain");
|
|
2015
|
+
return {
|
|
2016
|
+
tld: name.slice(name.indexOf(".") + 1),
|
|
2017
|
+
currency: price2.currency,
|
|
2018
|
+
registration: amount(price2.register),
|
|
2019
|
+
renewal: amount(price2.renew),
|
|
2020
|
+
transfer: amount(price2.transfer)
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
async getDnsRecords(domainName, opts) {
|
|
2024
|
+
return (await this.rawRecords(domainName, opts)).map(fromRecord);
|
|
2025
|
+
}
|
|
2026
|
+
// Reconcile by content-derived IDs. Preserve unchanged records, edit by ID, create additions
|
|
2027
|
+
// before removing stale records, and stop on any failed batch. Never retry an
|
|
2028
|
+
// uncertain write. This replaces the custom records, not the apex NS delegation.
|
|
2029
|
+
async setDnsRecords(domainName, records, opts) {
|
|
2030
|
+
const desired = records.map((r) => recordSpec(r, domainName));
|
|
2031
|
+
const unique = desired.filter(
|
|
2032
|
+
(r, i) => desired.findIndex((other) => recordSignature(other) === recordSignature(r)) === i
|
|
2033
|
+
);
|
|
2034
|
+
let changed = false;
|
|
2035
|
+
try {
|
|
2036
|
+
const stale = (await this.rawRecords(domainName, opts)).map((raw) => ({ raw, spec: recordSpec(fromRecord(raw), domainName, false) })).filter(({ spec }) => !(spec.type === "NS" && spec.name === ""));
|
|
2037
|
+
const additions = [];
|
|
2038
|
+
const edits = [];
|
|
2039
|
+
for (const record of unique) {
|
|
2040
|
+
const index = stale.findIndex(
|
|
2041
|
+
({ spec }) => recordSignature(spec) === recordSignature(record)
|
|
2042
|
+
);
|
|
2043
|
+
if (index >= 0) stale.splice(index, 1);
|
|
2044
|
+
else {
|
|
2045
|
+
const priorIndex = stale.findIndex(
|
|
2046
|
+
({ spec }) => spec.type === record.type && spec.name === record.name
|
|
2047
|
+
);
|
|
2048
|
+
const prior = priorIndex >= 0 ? stale.splice(priorIndex, 1)[0].raw : void 0;
|
|
2049
|
+
if (prior?.proxied) record.proxied = true;
|
|
2050
|
+
if (record.ttl === 1 && record.proxied !== true)
|
|
2051
|
+
throw new ConfigurationError(
|
|
2052
|
+
"101domain: automatic TTL requires an existing proxied SWA record"
|
|
2053
|
+
);
|
|
2054
|
+
if (prior) {
|
|
2055
|
+
const edit = { ...record, id: prior.id };
|
|
2056
|
+
delete edit.name;
|
|
2057
|
+
delete edit.type;
|
|
2058
|
+
edits.push(edit);
|
|
2059
|
+
} else additions.push(record);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
for (const batch of chunks(edits, 25)) {
|
|
2063
|
+
const response = await this.request({
|
|
2064
|
+
...opts,
|
|
2065
|
+
method: "PATCH",
|
|
2066
|
+
path: `${dnsPath(domainName)}/records`,
|
|
2067
|
+
body: { records: batch }
|
|
2068
|
+
});
|
|
2069
|
+
if (!Array.isArray(response.data) || response.data.length !== batch.length || new Set(response.data.map((r) => r?.old_id)).size !== batch.length || response.data.some(
|
|
2070
|
+
(r) => !r || !batch.some((edit) => edit.id === r.old_id) || typeof r.id !== "string" || !r.id
|
|
2071
|
+
))
|
|
2072
|
+
throw new OutcomeUnknownError(
|
|
2073
|
+
"101domain: invalid DNS edit confirmation. Re-read the zone before trying again."
|
|
2074
|
+
);
|
|
2075
|
+
changed = true;
|
|
2076
|
+
}
|
|
2077
|
+
for (const batch of chunks(additions, 25)) {
|
|
2078
|
+
await this.request({
|
|
2079
|
+
...opts,
|
|
2080
|
+
method: "POST",
|
|
2081
|
+
path: `${dnsPath(domainName)}/records`,
|
|
2082
|
+
body: { records: batch }
|
|
2083
|
+
});
|
|
2084
|
+
changed = true;
|
|
2085
|
+
}
|
|
2086
|
+
for (const batch of chunks(stale, 25)) {
|
|
2087
|
+
await this.request({
|
|
2088
|
+
...opts,
|
|
2089
|
+
method: "DELETE",
|
|
2090
|
+
path: `${dnsPath(domainName)}/records`,
|
|
2091
|
+
body: { ids: batch.map(({ raw }) => raw.id) }
|
|
2092
|
+
});
|
|
2093
|
+
changed = true;
|
|
2094
|
+
}
|
|
2095
|
+
return { success: true, message: "DNS records updated successfully" };
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
const result = failed(error);
|
|
2098
|
+
if (!changed && !result.outcome) return result;
|
|
2099
|
+
return {
|
|
2100
|
+
...result,
|
|
2101
|
+
message: `${result.message}. DNS replacement is not atomic; read the zone before retrying.`
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
async getDomainForwarding(domainName, opts) {
|
|
2106
|
+
const raw = (await this.request({
|
|
2107
|
+
path: domainPath(domainName),
|
|
2108
|
+
...opts
|
|
2109
|
+
})).data;
|
|
2110
|
+
if (!raw || normalizeDomain(raw.domain_name) !== normalizeDomain(domainName))
|
|
2111
|
+
throw invalid("domain identity");
|
|
2112
|
+
const forward = raw.web_forwarding;
|
|
2113
|
+
if (forward === null || forward?.destination === null && forward.type === null) return [];
|
|
2114
|
+
if (!forward || typeof forward.destination !== "string" || forward.type !== "301" && forward.type !== "cloak")
|
|
2115
|
+
throw invalid("forwarding");
|
|
2116
|
+
return [
|
|
2117
|
+
{
|
|
2118
|
+
host: "@",
|
|
2119
|
+
url: forward.destination,
|
|
2120
|
+
type: forward.type === "301" ? "permanent" : "masked"
|
|
2121
|
+
}
|
|
2122
|
+
];
|
|
2123
|
+
}
|
|
2124
|
+
async setDomainForwarding(domainName, forwards, opts) {
|
|
2125
|
+
if (forwards.length > 1 || forwards.some((f) => f.host !== "@" || f.type !== "permanent"))
|
|
2126
|
+
throw new ConfigurationError(
|
|
2127
|
+
"101domain: only one permanent apex forwarding rule is supported"
|
|
2128
|
+
);
|
|
2129
|
+
if (forwards.length) {
|
|
2130
|
+
let url;
|
|
2131
|
+
try {
|
|
2132
|
+
url = new URL(forwards[0].url);
|
|
2133
|
+
} catch {
|
|
2134
|
+
throw new ConfigurationError("101domain: forwarding needs a valid HTTP(S) URL");
|
|
2135
|
+
}
|
|
2136
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
|
|
2137
|
+
throw new ConfigurationError("101domain: forwarding needs a valid HTTP(S) URL");
|
|
2138
|
+
}
|
|
2139
|
+
try {
|
|
2140
|
+
const existing = await this.getDomainForwarding(domainName, opts);
|
|
2141
|
+
const path = `${domainPath(domainName)}/forwarding`;
|
|
2142
|
+
if (!forwards.length) {
|
|
2143
|
+
if (existing.length) await this.request({ ...opts, method: "DELETE", path });
|
|
2144
|
+
} else if (existing[0]?.url !== forwards[0].url || existing[0]?.type !== "permanent") {
|
|
2145
|
+
await this.request({
|
|
2146
|
+
...opts,
|
|
2147
|
+
method: existing.length ? "PATCH" : "POST",
|
|
2148
|
+
path,
|
|
2149
|
+
body: { destination: forwards[0].url, type: "301" }
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
return {
|
|
2153
|
+
success: true,
|
|
2154
|
+
message: forwards.length ? "URL forwarding updated" : "URL forwarding cleared"
|
|
2155
|
+
};
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
return failed(error);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
async request(req) {
|
|
2161
|
+
const raw = await this.http.request(req);
|
|
2162
|
+
if (!raw || typeof raw !== "object" || raw.status !== "success" || !("data" in raw)) {
|
|
2163
|
+
if (req.call?.intent === "write" && !REST_SAFE_METHODS.includes(req.method ?? "GET"))
|
|
2164
|
+
throw new OutcomeUnknownError(
|
|
2165
|
+
"101domain: invalid write confirmation. Re-read the domain before trying again."
|
|
2166
|
+
);
|
|
2167
|
+
throw invalid("response envelope");
|
|
2168
|
+
}
|
|
2169
|
+
return raw;
|
|
2170
|
+
}
|
|
2171
|
+
async rawRecords(domainName, opts) {
|
|
2172
|
+
const raw = (await this.request({ path: `${dnsPath(domainName)}/records`, ...opts })).data;
|
|
2173
|
+
if (!Array.isArray(raw) || raw.some(
|
|
2174
|
+
(r) => !r || typeof r.id !== "string" || !r.id || typeof r.name !== "string" || typeof r.value !== "string" || typeof r.type !== "string" || !Number.isInteger(r.ttl)
|
|
2175
|
+
))
|
|
2176
|
+
throw invalid("DNS records");
|
|
2177
|
+
if (new Set(raw.map((r) => r.id)).size !== raw.length) throw invalid("duplicate DNS ids");
|
|
2178
|
+
return raw;
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
function invalid(part) {
|
|
2182
|
+
return new InvalidResponseError(`101domain: invalid ${part}`);
|
|
2183
|
+
}
|
|
2184
|
+
function failed(error) {
|
|
2185
|
+
return {
|
|
2186
|
+
success: false,
|
|
2187
|
+
message: toRegistrarError(error).message,
|
|
2188
|
+
...error instanceof OutcomeUnknownError ? { outcome: "unknown" } : {}
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
function domainPath(name) {
|
|
2192
|
+
return `/domains/${encodeURIComponent(normalizeDomain(name))}`;
|
|
2193
|
+
}
|
|
2194
|
+
function dnsPath(name) {
|
|
2195
|
+
return `/dns/${encodeURIComponent(normalizeDomain(name))}`;
|
|
2196
|
+
}
|
|
2197
|
+
function chunks(items, size) {
|
|
2198
|
+
const result = [];
|
|
2199
|
+
for (let i = 0; i < items.length; i += size) result.push(items.slice(i, i + size));
|
|
2200
|
+
return result;
|
|
2201
|
+
}
|
|
2202
|
+
function oneYear(pricing) {
|
|
2203
|
+
if (pricing == null) return void 0;
|
|
2204
|
+
if (!Array.isArray(pricing)) throw invalid("pricing");
|
|
2205
|
+
return pricing.find((p) => p?.term_years === 1);
|
|
2206
|
+
}
|
|
2207
|
+
function amount(raw) {
|
|
2208
|
+
if (raw == null) return void 0;
|
|
2209
|
+
if (!/^\d+(?:\.\d+)?$/.test(raw)) throw invalid("price");
|
|
2210
|
+
const value = Number(raw);
|
|
2211
|
+
if (!Number.isFinite(value)) throw invalid("price");
|
|
2212
|
+
return value;
|
|
2213
|
+
}
|
|
2214
|
+
function toDomain(raw) {
|
|
2215
|
+
if (!raw || typeof raw.domain_name !== "string" || !raw.domain_name.trim())
|
|
2216
|
+
throw invalid("domain");
|
|
2217
|
+
return createDomain({
|
|
2218
|
+
domainName: normalizeDomain(raw.domain_name),
|
|
2219
|
+
registrar: "101domain",
|
|
2220
|
+
status: raw.status,
|
|
2221
|
+
createdDate: raw.registered_at,
|
|
2222
|
+
expirationDate: raw.expires_at,
|
|
2223
|
+
autoRenew: raw.auto_renew,
|
|
2224
|
+
locked: raw.registry_statuses?.some((s) => /^clientTransferProhibited$/i.test(s)),
|
|
2225
|
+
nameservers: raw.nameservers,
|
|
2226
|
+
deleted: !isHeld(raw)
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
var GONE = /* @__PURE__ */ new Set(["DELETED", "CANCELLED", "DENIED", "XFER_AWAY"]);
|
|
2230
|
+
function isHeld(raw) {
|
|
2231
|
+
const status = raw.status?.toUpperCase() ?? "";
|
|
2232
|
+
return !GONE.has(status) && !status.startsWith("XFER_IN_") && raw.registered_at != null;
|
|
2233
|
+
}
|
|
2234
|
+
function fromRecord(raw) {
|
|
2235
|
+
const common = { name: raw.name || "@", type: raw.type, ttl: raw.ttl };
|
|
2236
|
+
if (raw.type === "MX") {
|
|
2237
|
+
const match = /^(\d+)\s+(.+)$/.exec(raw.value);
|
|
2238
|
+
if (!match) throw invalid("MX record");
|
|
2239
|
+
return { ...common, value: canonicalValue("MX", match[2]), priority: Number(match[1]) };
|
|
2240
|
+
}
|
|
2241
|
+
if (raw.type === "SRV") {
|
|
2242
|
+
const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/.exec(raw.value);
|
|
2243
|
+
if (!match) throw invalid("SRV record");
|
|
2244
|
+
return {
|
|
2245
|
+
...common,
|
|
2246
|
+
value: canonicalValue("SRV", match[4]),
|
|
2247
|
+
priority: Number(match[1]),
|
|
2248
|
+
weight: Number(match[2]),
|
|
2249
|
+
port: Number(match[3])
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
return { ...common, value: canonicalValue(raw.type, raw.value) };
|
|
2253
|
+
}
|
|
2254
|
+
function canonicalValue(type, value) {
|
|
2255
|
+
const kind = type.toUpperCase();
|
|
2256
|
+
if (kind === "TXT") return unquoteTxt(value);
|
|
2257
|
+
if (["CNAME", "NS", "MX", "SRV"].includes(kind))
|
|
2258
|
+
return value === "." ? "." : value.replace(/\.$/, "");
|
|
2259
|
+
return value;
|
|
2260
|
+
}
|
|
2261
|
+
function unquoteTxt(value) {
|
|
2262
|
+
const chunk = /"((?:[^"\\]|\\.)*)"/g;
|
|
2263
|
+
if (!/^\s*"(?:[^"\\]|\\.)*"(?:\s+"(?:[^"\\]|\\.)*")*\s*$/.test(value)) return value;
|
|
2264
|
+
return [...value.matchAll(chunk)].map((m) => m[1].replace(/\\(.)/g, "$1")).join("");
|
|
2265
|
+
}
|
|
2266
|
+
function recordSpec(record, domain, strict = true) {
|
|
2267
|
+
const type = record.type.toUpperCase();
|
|
2268
|
+
const zone = normalizeDomain(domain);
|
|
2269
|
+
let name = record.name.trim().toLowerCase().replace(/\.$/, "");
|
|
2270
|
+
if (name === zone || name === "@") name = "";
|
|
2271
|
+
else if (name.endsWith("." + zone)) name = name.slice(0, -zone.length - 1);
|
|
2272
|
+
const reject = (message) => {
|
|
2273
|
+
if (strict) throw new ConfigurationError(`101domain: ${message}`);
|
|
2274
|
+
};
|
|
2275
|
+
if (!["A", "AAAA", "CNAME", "NS", "MX", "TXT", "SRV", "CAA"].includes(type) || type === "NS" && !name)
|
|
2276
|
+
reject("unsupported DNS type or apex NS record");
|
|
2277
|
+
const ttl = record.ttl ?? 3600;
|
|
2278
|
+
if (!Number.isInteger(ttl) || ttl !== 1 && ttl < 300)
|
|
2279
|
+
reject("DNS TTL must be at least 300 seconds");
|
|
2280
|
+
const result = { name, type, ttl };
|
|
2281
|
+
if (type === "MX" || type === "SRV") {
|
|
2282
|
+
if (!Number.isInteger(record.priority) || record.priority < 0 || record.priority > 65535)
|
|
2283
|
+
reject("MX/SRV priority is required");
|
|
2284
|
+
result.priority = record.priority;
|
|
2285
|
+
result.target = canonicalValue(type, record.value);
|
|
2286
|
+
if (type === "SRV") {
|
|
2287
|
+
if (![record.weight, record.port].every((n) => Number.isInteger(n) && n >= 0 && n <= 65535))
|
|
2288
|
+
reject("SRV weight and port are required");
|
|
2289
|
+
result.weight = record.weight;
|
|
2290
|
+
result.port = record.port;
|
|
2291
|
+
}
|
|
2292
|
+
} else if (type === "CAA") {
|
|
2293
|
+
const match = /^(\d+)\s+(\w+)\s+"([\s\S]*)"$/.exec(record.value);
|
|
2294
|
+
if (!match || Number(match[1]) > 255) {
|
|
2295
|
+
reject("CAA needs flags, tag and quoted value");
|
|
2296
|
+
result.value = record.value;
|
|
2297
|
+
} else {
|
|
2298
|
+
result.flag = Number(match[1]);
|
|
2299
|
+
result.tag = match[2];
|
|
2300
|
+
result.value = match[3];
|
|
2301
|
+
}
|
|
2302
|
+
} else result.value = canonicalValue(type, record.value);
|
|
2303
|
+
return result;
|
|
2304
|
+
}
|
|
2305
|
+
function recordSignature(record) {
|
|
2306
|
+
return JSON.stringify(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
2307
|
+
}
|
|
2308
|
+
|
|
1776
2309
|
// src/registrars/dynadot.ts
|
|
1777
2310
|
var DYNADOT_DEFAULT_NS = ["ns1.dynadot.com", "ns2.dynadot.com"];
|
|
1778
2311
|
var SEARCH_BATCH_SIZE = 100;
|
|
@@ -2450,12 +2983,12 @@ function isPrivacyOn(privacy) {
|
|
|
2450
2983
|
}
|
|
2451
2984
|
function oneYearPrice(prices) {
|
|
2452
2985
|
if (!prices?.length) return void 0;
|
|
2453
|
-
const
|
|
2986
|
+
const oneYear2 = prices.find((p) => /\b1\s*year\b/i.test(p.unit ?? "")) ?? prices[0];
|
|
2454
2987
|
return {
|
|
2455
|
-
currency:
|
|
2456
|
-
registration: toNumber(
|
|
2457
|
-
renewal: toNumber(
|
|
2458
|
-
transfer: toNumber(
|
|
2988
|
+
currency: oneYear2.currency,
|
|
2989
|
+
registration: toNumber(oneYear2.registration_price),
|
|
2990
|
+
renewal: toNumber(oneYear2.renewal_price),
|
|
2991
|
+
transfer: toNumber(oneYear2.transfer_price)
|
|
2459
2992
|
};
|
|
2460
2993
|
}
|
|
2461
2994
|
function extractNameservers(glue) {
|
|
@@ -2745,8 +3278,8 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
2745
3278
|
available: status.startsWith("available"),
|
|
2746
3279
|
premium: status.includes("premium") || price2?.type === "premium"
|
|
2747
3280
|
};
|
|
2748
|
-
const
|
|
2749
|
-
if (
|
|
3281
|
+
const amount2 = pickPrice(price2);
|
|
3282
|
+
if (amount2 != null) result.price = amount2;
|
|
2750
3283
|
if (resp.currency) result.currency = resp.currency;
|
|
2751
3284
|
if (price2?.duration_unit === "y" && price2.min_duration != null) {
|
|
2752
3285
|
result.period = price2.min_duration;
|
|
@@ -5259,7 +5792,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5259
5792
|
}
|
|
5260
5793
|
async getAuthCode(domainName, opts) {
|
|
5261
5794
|
const result = await this.http.request({
|
|
5262
|
-
path: `${
|
|
5795
|
+
path: `${domainPath2(domainName)}:getAuthCode`,
|
|
5263
5796
|
...opts
|
|
5264
5797
|
});
|
|
5265
5798
|
if (!result?.authCode) throw new InvalidResponseError("namecom: missing authorization code");
|
|
@@ -5354,7 +5887,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5354
5887
|
validYears(years);
|
|
5355
5888
|
const pricing = await this.domainPricing(domainName, years, opts);
|
|
5356
5889
|
return this.purchase(
|
|
5357
|
-
`${
|
|
5890
|
+
`${domainPath2(domainName)}:renew`,
|
|
5358
5891
|
{
|
|
5359
5892
|
years,
|
|
5360
5893
|
purchasePrice: purchaseQuote(pricing, "renewalPrice")
|
|
@@ -5399,7 +5932,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5399
5932
|
}
|
|
5400
5933
|
updateNameservers(domainName, nameservers, opts) {
|
|
5401
5934
|
return this.mutate(
|
|
5402
|
-
{ method: "POST", path: `${
|
|
5935
|
+
{ method: "POST", path: `${domainPath2(domainName)}:setNameservers`, body: { nameservers } },
|
|
5403
5936
|
opts
|
|
5404
5937
|
);
|
|
5405
5938
|
}
|
|
@@ -5430,14 +5963,14 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5430
5963
|
return this.mutate(
|
|
5431
5964
|
{
|
|
5432
5965
|
method: "POST",
|
|
5433
|
-
path: `${
|
|
5966
|
+
path: `${domainPath2(domainName)}:setContacts`,
|
|
5434
5967
|
body: { contacts: toContacts(contacts) }
|
|
5435
5968
|
},
|
|
5436
5969
|
opts
|
|
5437
5970
|
);
|
|
5438
5971
|
}
|
|
5439
5972
|
async getDnsRecords(domainName, opts) {
|
|
5440
|
-
return (await this.pages(`${
|
|
5973
|
+
return (await this.pages(`${domainPath2(domainName)}/records`, "records", opts)).map(fromRecord2);
|
|
5441
5974
|
}
|
|
5442
5975
|
// No bulk replace endpoint. Reconcile by id: preserve identical records, update
|
|
5443
5976
|
// existing type/host pairs, create additions, then delete stale records. This
|
|
@@ -5445,7 +5978,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5445
5978
|
async setDnsRecords(domainName, records, opts) {
|
|
5446
5979
|
const desired = records.map(toRecord).filter((record, index, all) => all.findIndex((other) => sameRecord(other, record)) === index);
|
|
5447
5980
|
try {
|
|
5448
|
-
const path = `${
|
|
5981
|
+
const path = `${domainPath2(domainName)}/records`;
|
|
5449
5982
|
const existing = await this.pages(path, "records", opts);
|
|
5450
5983
|
if (existing.some((r) => !Number.isInteger(r.id) || r.id <= 0))
|
|
5451
5984
|
throw new InvalidResponseError("namecom: DNS response missing record ids");
|
|
@@ -5482,14 +6015,14 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5482
6015
|
}
|
|
5483
6016
|
}
|
|
5484
6017
|
patchDomain(domainName, body, opts) {
|
|
5485
|
-
return this.mutate({ method: "PATCH", path:
|
|
6018
|
+
return this.mutate({ method: "PATCH", path: domainPath2(domainName), body }, opts);
|
|
5486
6019
|
}
|
|
5487
6020
|
rawDomain(domainName, opts) {
|
|
5488
|
-
return this.http.request({ path:
|
|
6021
|
+
return this.http.request({ path: domainPath2(domainName), ...opts });
|
|
5489
6022
|
}
|
|
5490
6023
|
domainPricing(domainName, years, opts) {
|
|
5491
6024
|
return this.http.request({
|
|
5492
|
-
path: `${
|
|
6025
|
+
path: `${domainPath2(domainName)}:getPricing`,
|
|
5493
6026
|
query: { years },
|
|
5494
6027
|
...opts
|
|
5495
6028
|
});
|
|
@@ -5605,7 +6138,7 @@ function toContacts(contacts) {
|
|
|
5605
6138
|
throw new ConfigurationError("namecom: at least one contact is required");
|
|
5606
6139
|
return result;
|
|
5607
6140
|
}
|
|
5608
|
-
function
|
|
6141
|
+
function domainPath2(domainName) {
|
|
5609
6142
|
return `/domains/${encodeURIComponent(normalizeDomain(domainName))}`;
|
|
5610
6143
|
}
|
|
5611
6144
|
function validYears(years) {
|
|
@@ -5645,7 +6178,7 @@ function toRecord(r) {
|
|
|
5645
6178
|
...["MX", "SRV"].includes(type) ? { priority: r.priority ?? 0 } : {}
|
|
5646
6179
|
};
|
|
5647
6180
|
}
|
|
5648
|
-
function
|
|
6181
|
+
function fromRecord2(r) {
|
|
5649
6182
|
const type = r.type.toUpperCase();
|
|
5650
6183
|
const record = { type, name: r.host || "@", value: r.answer, ttl: r.ttl };
|
|
5651
6184
|
if (type === "MX" || type === "SRV") record.priority = r.priority ?? 0;
|
|
@@ -7466,22 +7999,347 @@ function toSpaceshipContact(c) {
|
|
|
7466
7999
|
|
|
7467
8000
|
// src/registrars/index.ts
|
|
7468
8001
|
var registrars = {
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
8002
|
+
"101domain": Domain101Registrar,
|
|
8003
|
+
"cloudflare": CloudflareRegistrar,
|
|
8004
|
+
"dynadot": DynadotRegistrar,
|
|
8005
|
+
"gandi": GandiRegistrar,
|
|
8006
|
+
"godaddy": GoDaddyRegistrar,
|
|
8007
|
+
"namebright": NameBrightRegistrar,
|
|
8008
|
+
"namecheap": NamecheapRegistrar,
|
|
8009
|
+
"namecom": NamecomRegistrar,
|
|
8010
|
+
"namesilo": NameSiloRegistrar,
|
|
8011
|
+
"porkbun": PorkbunRegistrar,
|
|
8012
|
+
"spaceship": SpaceshipRegistrar
|
|
7479
8013
|
};
|
|
7480
8014
|
function createRegistrar(name, credentials, options) {
|
|
7481
8015
|
const RegistrarClass = registrars[name];
|
|
7482
8016
|
return new RegistrarClass(credentials, options);
|
|
7483
8017
|
}
|
|
7484
8018
|
|
|
7485
|
-
|
|
8019
|
+
// src/dns-records.ts
|
|
8020
|
+
var DnsSyntaxError = class extends Error {
|
|
8021
|
+
constructor(message) {
|
|
8022
|
+
super(message);
|
|
8023
|
+
this.name = "DnsSyntaxError";
|
|
8024
|
+
}
|
|
8025
|
+
};
|
|
8026
|
+
var NAME_LIMIT = 253;
|
|
8027
|
+
var LABEL_LIMIT = 63;
|
|
8028
|
+
var TTL_LIMIT = 2147483647;
|
|
8029
|
+
var TEXT_OCTETS = 255;
|
|
8030
|
+
var TEXT_TOTAL_OCTETS = 65535 - Math.ceil(65535 / (TEXT_OCTETS + 1));
|
|
8031
|
+
var UINT16_MAX = 65535;
|
|
8032
|
+
var DIGEST_HEX_LENGTH = {
|
|
8033
|
+
1: 40,
|
|
8034
|
+
2: 64,
|
|
8035
|
+
3: 64,
|
|
8036
|
+
4: 96
|
|
8037
|
+
};
|
|
8038
|
+
var KINDS_ALLOWED_BESIDE_CNAME = /* @__PURE__ */ new Set(["CNAME", "RRSIG", "NSEC", "NSEC3"]);
|
|
8039
|
+
function mapAsciiLabel(label) {
|
|
8040
|
+
const folded = label.toLowerCase();
|
|
8041
|
+
if (folded.length < 1 || folded.length > LABEL_LIMIT) return null;
|
|
8042
|
+
if (!/^[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?$/.test(folded)) return null;
|
|
8043
|
+
return folded;
|
|
8044
|
+
}
|
|
8045
|
+
var labelMapper = mapAsciiLabel;
|
|
8046
|
+
function useLabelMapper(mapper) {
|
|
8047
|
+
labelMapper = mapper;
|
|
8048
|
+
}
|
|
8049
|
+
function resetLabelMapper() {
|
|
8050
|
+
labelMapper = mapAsciiLabel;
|
|
8051
|
+
}
|
|
8052
|
+
function prepareName(input, role = "owner") {
|
|
8053
|
+
const trimmed = input.trim();
|
|
8054
|
+
if (!trimmed) throw new DnsSyntaxError("Name is empty");
|
|
8055
|
+
const bare = trimmed.endsWith(".") ? trimmed.slice(0, -1) : trimmed;
|
|
8056
|
+
if (!bare) throw new DnsSyntaxError("Name is empty");
|
|
8057
|
+
if (bare === "@") {
|
|
8058
|
+
if (role === "target") throw new DnsSyntaxError("Origin token @ cannot be a record target");
|
|
8059
|
+
return "@";
|
|
8060
|
+
}
|
|
8061
|
+
const labels = bare.split(".");
|
|
8062
|
+
if (labels.some((label) => label.length === 0)) {
|
|
8063
|
+
throw new DnsSyntaxError("Name contains an empty label");
|
|
8064
|
+
}
|
|
8065
|
+
const starLabels = labels.filter((label) => label.includes("*"));
|
|
8066
|
+
if (starLabels.length > 0) {
|
|
8067
|
+
if (role !== "owner") {
|
|
8068
|
+
throw new DnsSyntaxError("A wildcard is only valid as an owner name");
|
|
8069
|
+
}
|
|
8070
|
+
const starCount = starLabels.reduce(
|
|
8071
|
+
(count, label) => count + (label.match(/\*/g)?.length ?? 0),
|
|
8072
|
+
0
|
|
8073
|
+
);
|
|
8074
|
+
if (starCount > 1) throw new DnsSyntaxError("A name can contain only one wildcard");
|
|
8075
|
+
if (labels[0] !== "*") throw new DnsSyntaxError("A wildcard must be its own leftmost label");
|
|
8076
|
+
}
|
|
8077
|
+
const mapped = labels.map((label) => {
|
|
8078
|
+
if (label === "*") return "*";
|
|
8079
|
+
const ascii = labelMapper(label);
|
|
8080
|
+
if (!ascii) throw new DnsSyntaxError(`Label is not a valid DNS label: ${label}`);
|
|
8081
|
+
return ascii.toLowerCase();
|
|
8082
|
+
});
|
|
8083
|
+
const name = mapped.join(".");
|
|
8084
|
+
if (name.length > NAME_LIMIT) throw new DnsSyntaxError("Name is longer than 253 characters");
|
|
8085
|
+
return name;
|
|
8086
|
+
}
|
|
8087
|
+
function isIpv4(text2) {
|
|
8088
|
+
const octets = text2.split(".");
|
|
8089
|
+
if (octets.length !== 4) return false;
|
|
8090
|
+
return octets.every((octet) => {
|
|
8091
|
+
if (!/^\d{1,3}$/.test(octet)) return false;
|
|
8092
|
+
const value = Number(octet);
|
|
8093
|
+
return value >= 0 && value <= 255 && String(value) === octet;
|
|
8094
|
+
});
|
|
8095
|
+
}
|
|
8096
|
+
function isIpv6(text2) {
|
|
8097
|
+
let body = text2;
|
|
8098
|
+
const embedded = text2.match(/^(.+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
8099
|
+
if (embedded) {
|
|
8100
|
+
if (!isIpv4(embedded[2])) return false;
|
|
8101
|
+
body = `${embedded[1]}:0:0`;
|
|
8102
|
+
}
|
|
8103
|
+
const compressedAt = body.indexOf("::");
|
|
8104
|
+
if (compressedAt !== -1) {
|
|
8105
|
+
if (body.indexOf("::", compressedAt + 1) !== -1) return false;
|
|
8106
|
+
const groupsOf = (side) => side === "" ? [] : side.split(":");
|
|
8107
|
+
const head = groupsOf(body.slice(0, compressedAt));
|
|
8108
|
+
const tail = groupsOf(body.slice(compressedAt + 2));
|
|
8109
|
+
if (head.length + tail.length > 7) return false;
|
|
8110
|
+
return [...head, ...tail].every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
8111
|
+
}
|
|
8112
|
+
const groups = body.split(":");
|
|
8113
|
+
return groups.length === 8 && groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
8114
|
+
}
|
|
8115
|
+
function utf8OctetCount(text2) {
|
|
8116
|
+
let count = 0;
|
|
8117
|
+
for (const char of text2) {
|
|
8118
|
+
const code = char.codePointAt(0) ?? 0;
|
|
8119
|
+
if (code <= 127) count += 1;
|
|
8120
|
+
else if (code <= 2047) count += 2;
|
|
8121
|
+
else if (code <= 65535) count += 3;
|
|
8122
|
+
else count += 4;
|
|
8123
|
+
}
|
|
8124
|
+
return count;
|
|
8125
|
+
}
|
|
8126
|
+
function decimalInRange(token, limit) {
|
|
8127
|
+
if (!/^(0|[1-9][0-9]*)$/.test(token)) return null;
|
|
8128
|
+
const value = Number(token);
|
|
8129
|
+
if (value > limit) return null;
|
|
8130
|
+
return value;
|
|
8131
|
+
}
|
|
8132
|
+
function prepareIpv4(text2) {
|
|
8133
|
+
if (!isIpv4(text2)) throw new DnsSyntaxError("IPv4 address is invalid");
|
|
8134
|
+
return text2;
|
|
8135
|
+
}
|
|
8136
|
+
function prepareIpv6(text2) {
|
|
8137
|
+
if (!isIpv6(text2)) throw new DnsSyntaxError("IPv6 address is invalid");
|
|
8138
|
+
return text2.toLowerCase();
|
|
8139
|
+
}
|
|
8140
|
+
function prepareHostTarget(text2, owner, rejectSelf) {
|
|
8141
|
+
const withoutDot = text2.endsWith(".") ? text2.slice(0, -1) : text2;
|
|
8142
|
+
if (isIpv4(withoutDot) || isIpv6(withoutDot)) {
|
|
8143
|
+
throw new DnsSyntaxError("Target must be a domain name, not an address");
|
|
8144
|
+
}
|
|
8145
|
+
const target = prepareName(text2, "target");
|
|
8146
|
+
if (rejectSelf && owner !== void 0 && prepareName(owner, "owner") === target) {
|
|
8147
|
+
throw new DnsSyntaxError("CNAME target is the same name as its owner");
|
|
8148
|
+
}
|
|
8149
|
+
return target;
|
|
8150
|
+
}
|
|
8151
|
+
function prepareMailExchange(text2) {
|
|
8152
|
+
const parts = text2.split(/\s+/);
|
|
8153
|
+
if (parts.length !== 2) {
|
|
8154
|
+
throw new DnsSyntaxError('MX data must be "<priority> <mail host>"');
|
|
8155
|
+
}
|
|
8156
|
+
const priority = decimalInRange(parts[0], UINT16_MAX);
|
|
8157
|
+
if (priority === null) throw new DnsSyntaxError("MX priority must be an integer from 0 to 65535");
|
|
8158
|
+
return `${priority} ${prepareServiceHost(parts[1])}`;
|
|
8159
|
+
}
|
|
8160
|
+
function prepareServiceHost(text2) {
|
|
8161
|
+
return text2 === "." ? "." : prepareHostTarget(text2, void 0, false);
|
|
8162
|
+
}
|
|
8163
|
+
function prepareService(text2) {
|
|
8164
|
+
const parts = text2.split(/\s+/);
|
|
8165
|
+
if (parts.length !== 4) {
|
|
8166
|
+
throw new DnsSyntaxError('SRV data must be "<priority> <weight> <port> <target>"');
|
|
8167
|
+
}
|
|
8168
|
+
const [priority, weight, port] = parts.slice(0, 3).map((part) => decimalInRange(part, UINT16_MAX));
|
|
8169
|
+
if (priority === null)
|
|
8170
|
+
throw new DnsSyntaxError("SRV priority must be an integer from 0 to 65535");
|
|
8171
|
+
if (weight === null) throw new DnsSyntaxError("SRV weight must be an integer from 0 to 65535");
|
|
8172
|
+
if (port === null) throw new DnsSyntaxError("SRV port must be an integer from 0 to 65535");
|
|
8173
|
+
return `${priority} ${weight} ${port} ${prepareServiceHost(parts[3])}`;
|
|
8174
|
+
}
|
|
8175
|
+
function prepareAuthorization(text2) {
|
|
8176
|
+
const match = /^(\S+)\s+(\S+)\s+([\s\S]+)$/.exec(text2);
|
|
8177
|
+
if (!match) throw new DnsSyntaxError('CAA data must be "<flags> <tag> <value>"');
|
|
8178
|
+
const flags = decimalInRange(match[1], 255);
|
|
8179
|
+
if (flags === null) throw new DnsSyntaxError("CAA flags must be an integer from 0 to 255");
|
|
8180
|
+
if (!/^[a-z0-9]{1,15}$/i.test(match[2])) {
|
|
8181
|
+
throw new DnsSyntaxError("CAA tag must be 1 to 15 letters or digits");
|
|
8182
|
+
}
|
|
8183
|
+
const quoted = /^"((?:[^"\\]|\\.)*)"$/.exec(match[3]);
|
|
8184
|
+
const value = quoted ? quoted[1] : match[3];
|
|
8185
|
+
if (!quoted && value.includes('"')) {
|
|
8186
|
+
throw new DnsSyntaxError("CAA value must be one quoted or unquoted string");
|
|
8187
|
+
}
|
|
8188
|
+
return `${flags} ${match[2].toLowerCase()} "${value}"`;
|
|
8189
|
+
}
|
|
8190
|
+
function prepareText(text2) {
|
|
8191
|
+
if (utf8OctetCount(text2) > TEXT_TOTAL_OCTETS) {
|
|
8192
|
+
throw new DnsSyntaxError(`TXT data is longer than ${TEXT_TOTAL_OCTETS} octets`);
|
|
8193
|
+
}
|
|
8194
|
+
return text2;
|
|
8195
|
+
}
|
|
8196
|
+
function readSignerFields(text2) {
|
|
8197
|
+
const parts = text2.trim().split(/\s+/);
|
|
8198
|
+
if (parts.length !== 4)
|
|
8199
|
+
throw new DnsSyntaxError('DS data must be "<key tag> <algorithm> <digest type> <digest>"');
|
|
8200
|
+
const keyTag = decimalInRange(parts[0], UINT16_MAX);
|
|
8201
|
+
const algorithm = decimalInRange(parts[1], 255);
|
|
8202
|
+
const digestType = decimalInRange(parts[2], 255);
|
|
8203
|
+
if (keyTag === null || algorithm === null || digestType === null || !parts[3]) {
|
|
8204
|
+
throw new DnsSyntaxError('DS data must be "<key tag> <algorithm> <digest type> <digest>"');
|
|
8205
|
+
}
|
|
8206
|
+
const digest = parts[3].toLowerCase();
|
|
8207
|
+
if (!/^[0-9a-f]+$/.test(digest)) throw new DnsSyntaxError("DS digest must be hexadecimal");
|
|
8208
|
+
const expectedLength = DIGEST_HEX_LENGTH[digestType];
|
|
8209
|
+
if (expectedLength !== void 0 && digest.length !== expectedLength) {
|
|
8210
|
+
throw new DnsSyntaxError(
|
|
8211
|
+
`DS digest must be ${expectedLength} hex characters for digest type ${digestType}`
|
|
8212
|
+
);
|
|
8213
|
+
}
|
|
8214
|
+
return { keyTag, algorithm, digestType, digest };
|
|
8215
|
+
}
|
|
8216
|
+
function readDelegationSigner(rdata) {
|
|
8217
|
+
return readSignerFields(rdata);
|
|
8218
|
+
}
|
|
8219
|
+
function prepareSignerText(text2) {
|
|
8220
|
+
const signer = readSignerFields(text2);
|
|
8221
|
+
return `${signer.keyTag} ${signer.algorithm} ${signer.digestType} ${signer.digest}`;
|
|
8222
|
+
}
|
|
8223
|
+
function prepareRdata(kind, rdata, owner) {
|
|
8224
|
+
const token = kind.trim().toUpperCase();
|
|
8225
|
+
const text2 = rdata.trim();
|
|
8226
|
+
if (!text2) throw new DnsSyntaxError("Record data is empty");
|
|
8227
|
+
switch (token) {
|
|
8228
|
+
case "A":
|
|
8229
|
+
return prepareIpv4(text2);
|
|
8230
|
+
case "AAAA":
|
|
8231
|
+
return prepareIpv6(text2);
|
|
8232
|
+
case "CNAME":
|
|
8233
|
+
return prepareHostTarget(text2, owner, true);
|
|
8234
|
+
case "NS":
|
|
8235
|
+
return prepareHostTarget(text2, void 0, false);
|
|
8236
|
+
case "MX":
|
|
8237
|
+
return prepareMailExchange(text2);
|
|
8238
|
+
case "TXT":
|
|
8239
|
+
return prepareText(text2);
|
|
8240
|
+
case "SRV":
|
|
8241
|
+
return prepareService(text2);
|
|
8242
|
+
case "CAA":
|
|
8243
|
+
return prepareAuthorization(text2);
|
|
8244
|
+
case "DS":
|
|
8245
|
+
return prepareSignerText(text2);
|
|
8246
|
+
default:
|
|
8247
|
+
return text2;
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8250
|
+
function prepareEntry(entry) {
|
|
8251
|
+
if (!Number.isInteger(entry.ttl) || entry.ttl < 0 || entry.ttl > TTL_LIMIT) {
|
|
8252
|
+
throw new DnsSyntaxError("TTL must be an integer from 0 to 2147483647");
|
|
8253
|
+
}
|
|
8254
|
+
const kind = entry.kind.trim().toUpperCase();
|
|
8255
|
+
if (!kind) throw new DnsSyntaxError("Record type is empty");
|
|
8256
|
+
return {
|
|
8257
|
+
owner: prepareName(entry.owner, "owner"),
|
|
8258
|
+
kind,
|
|
8259
|
+
ttl: entry.ttl,
|
|
8260
|
+
rdata: prepareRdata(kind, entry.rdata, entry.owner)
|
|
8261
|
+
};
|
|
8262
|
+
}
|
|
8263
|
+
function syntaxClash(entry, error) {
|
|
8264
|
+
const detail = error instanceof Error ? error.message : "Invalid record";
|
|
8265
|
+
return { owner: entry.owner, kind: entry.kind, detail };
|
|
8266
|
+
}
|
|
8267
|
+
function findCnameConflicts(entries) {
|
|
8268
|
+
const clashes = [];
|
|
8269
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
8270
|
+
for (const entry of entries) {
|
|
8271
|
+
let owner;
|
|
8272
|
+
let kind;
|
|
8273
|
+
try {
|
|
8274
|
+
owner = prepareName(entry.owner, "owner");
|
|
8275
|
+
kind = entry.kind.trim().toUpperCase();
|
|
8276
|
+
if (!kind) throw new DnsSyntaxError("Record type is empty");
|
|
8277
|
+
} catch (error) {
|
|
8278
|
+
clashes.push(syntaxClash(entry, error));
|
|
8279
|
+
continue;
|
|
8280
|
+
}
|
|
8281
|
+
const group = byOwner.get(owner) ?? [];
|
|
8282
|
+
group.push({ ...entry, owner, kind });
|
|
8283
|
+
byOwner.set(owner, group);
|
|
8284
|
+
}
|
|
8285
|
+
for (const [owner, group] of byOwner) {
|
|
8286
|
+
const aliasCount = group.filter((entry) => entry.kind === "CNAME").length;
|
|
8287
|
+
if (aliasCount === 0) continue;
|
|
8288
|
+
if (aliasCount > 1) {
|
|
8289
|
+
clashes.push({
|
|
8290
|
+
owner,
|
|
8291
|
+
kind: "CNAME",
|
|
8292
|
+
detail: "A name can have only one CNAME target"
|
|
8293
|
+
});
|
|
8294
|
+
}
|
|
8295
|
+
const others = [...new Set(group.map((entry) => entry.kind))].filter(
|
|
8296
|
+
(kind) => !KINDS_ALLOWED_BESIDE_CNAME.has(kind)
|
|
8297
|
+
);
|
|
8298
|
+
if (others.length > 0) {
|
|
8299
|
+
clashes.push({
|
|
8300
|
+
owner,
|
|
8301
|
+
kind: "CNAME",
|
|
8302
|
+
detail: `A CNAME cannot share a name with ${others.join(", ")}`
|
|
8303
|
+
});
|
|
8304
|
+
}
|
|
8305
|
+
}
|
|
8306
|
+
return clashes;
|
|
8307
|
+
}
|
|
8308
|
+
function findRepeatedRecords(entries) {
|
|
8309
|
+
const clashes = [];
|
|
8310
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8311
|
+
for (const entry of entries) {
|
|
8312
|
+
let prepared;
|
|
8313
|
+
try {
|
|
8314
|
+
prepared = prepareEntry(entry);
|
|
8315
|
+
} catch (error) {
|
|
8316
|
+
clashes.push(syntaxClash(entry, error));
|
|
8317
|
+
continue;
|
|
8318
|
+
}
|
|
8319
|
+
if (prepared.kind === "CNAME") continue;
|
|
8320
|
+
const key = `${prepared.owner}\0${prepared.kind}\0${prepared.rdata}`;
|
|
8321
|
+
if (seen.has(key)) {
|
|
8322
|
+
clashes.push({
|
|
8323
|
+
owner: prepared.owner,
|
|
8324
|
+
kind: prepared.kind,
|
|
8325
|
+
detail: `Repeated ${prepared.kind} data at this name`
|
|
8326
|
+
});
|
|
8327
|
+
}
|
|
8328
|
+
seen.add(key);
|
|
8329
|
+
}
|
|
8330
|
+
return clashes;
|
|
8331
|
+
}
|
|
8332
|
+
function toDnsEntry(record, defaultTtl = 3600) {
|
|
8333
|
+
const kind = record.type.trim().toUpperCase();
|
|
8334
|
+
const numbers = kind === "MX" ? [record.priority] : kind === "SRV" ? [record.priority, record.weight, record.port] : [];
|
|
8335
|
+
return {
|
|
8336
|
+
owner: record.name,
|
|
8337
|
+
kind,
|
|
8338
|
+
ttl: record.ttl ?? defaultTtl,
|
|
8339
|
+
rdata: [...numbers.map((n) => n === void 0 ? "" : String(n)), record.value.trim()].join(" ")
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
|
|
8343
|
+
export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DnsSyntaxError, Domain101Registrar, DynadotRegistrar, EXTENDED_FEATURES, FEATURE_CALLS, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, OutcomeUnknownError, ParsingError, PorkbunRegistrar, REST_SAFE_METHODS, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, useLabelMapper, wasNotSent };
|
|
7486
8344
|
//# sourceMappingURL=index.js.map
|
|
7487
8345
|
//# sourceMappingURL=index.js.map
|