@aoxborrow/registrar-client 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -43
- package/dist/index.cjs +555 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +555 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/registrars/domain101.ts +709 -0
- package/src/registrars/index.ts +12 -10
- package/src/types.ts +3 -0
package/dist/index.cjs
CHANGED
|
@@ -1078,8 +1078,8 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1078
1078
|
message: `${domainName} is not a zone in this account; DNS is managed via the Zones API`
|
|
1079
1079
|
};
|
|
1080
1080
|
}
|
|
1081
|
-
const
|
|
1082
|
-
const list = await this.http.request({ path:
|
|
1081
|
+
const dnsPath2 = `/zones/${encodeURIComponent(zone.id)}/dns_records`;
|
|
1082
|
+
const list = await this.http.request({ path: dnsPath2, ...opts });
|
|
1083
1083
|
if (!list.success) {
|
|
1084
1084
|
return {
|
|
1085
1085
|
success: false,
|
|
@@ -1090,7 +1090,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1090
1090
|
if (!rec.id) continue;
|
|
1091
1091
|
const del = await this.http.request({
|
|
1092
1092
|
method: "DELETE",
|
|
1093
|
-
path: `${
|
|
1093
|
+
path: `${dnsPath2}/${encodeURIComponent(rec.id)}`,
|
|
1094
1094
|
...opts
|
|
1095
1095
|
});
|
|
1096
1096
|
if (!del.success) {
|
|
@@ -1106,7 +1106,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1106
1106
|
if (type === "NS" && name === zone.name) continue;
|
|
1107
1107
|
const create = await this.http.request({
|
|
1108
1108
|
method: "POST",
|
|
1109
|
-
path:
|
|
1109
|
+
path: dnsPath2,
|
|
1110
1110
|
body: toCfDnsBody(record, type, name),
|
|
1111
1111
|
...opts
|
|
1112
1112
|
});
|
|
@@ -1775,6 +1775,527 @@ function toContact(c) {
|
|
|
1775
1775
|
return contact;
|
|
1776
1776
|
}
|
|
1777
1777
|
|
|
1778
|
+
// src/registrars/domain101.ts
|
|
1779
|
+
var Domain101HttpClient = class extends HttpClient {
|
|
1780
|
+
async request(req) {
|
|
1781
|
+
try {
|
|
1782
|
+
return await super.request(req);
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
if (error instanceof AuthorizationError) {
|
|
1785
|
+
const scope = requiredScope(req);
|
|
1786
|
+
if (!scope || error.providerCode !== "INSUFFICIENT_PERMISSIONS") throw error;
|
|
1787
|
+
const scoped = new AuthorizationError(
|
|
1788
|
+
`101domain: this API key does not have the ${scope} scope. Create a key with ${scope} to ${SCOPE_PURPOSE[scope]}.`
|
|
1789
|
+
);
|
|
1790
|
+
scoped.providerCode = error.providerCode;
|
|
1791
|
+
throw scoped;
|
|
1792
|
+
}
|
|
1793
|
+
if (error instanceof OutcomeUnknownError)
|
|
1794
|
+
throw new OutcomeUnknownError(
|
|
1795
|
+
"101domain: the registrar did not confirm this change. Re-read the domain before trying again.",
|
|
1796
|
+
{ feature: error.feature }
|
|
1797
|
+
);
|
|
1798
|
+
if (error instanceof ParsingError) throw new ParsingError("101domain: invalid JSON response");
|
|
1799
|
+
if (error instanceof ConnectionError)
|
|
1800
|
+
throw new ConnectionError("101domain: network request failed", { notSent: error.notSent });
|
|
1801
|
+
throw error;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
async toStatusError(response, url) {
|
|
1805
|
+
const detail = await errorDetail(response);
|
|
1806
|
+
const error = await super.toStatusError(
|
|
1807
|
+
new Response(null, { status: response.status, headers: response.headers }),
|
|
1808
|
+
url
|
|
1809
|
+
);
|
|
1810
|
+
if (detail) {
|
|
1811
|
+
error.message = `101domain: ${detail.message}`;
|
|
1812
|
+
error.providerCode = detail.code;
|
|
1813
|
+
} else if (response.status === 403) {
|
|
1814
|
+
error.message = "101domain: the request was refused before reaching the API (403 without an API error)";
|
|
1815
|
+
}
|
|
1816
|
+
return error;
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
var SCOPE_PURPOSE = {
|
|
1820
|
+
domains_read: "read the domain portfolio",
|
|
1821
|
+
domains_write: "change URL forwarding",
|
|
1822
|
+
dns_read: "read DNS records and nameservers",
|
|
1823
|
+
dns_write: "change DNS records and nameservers"
|
|
1824
|
+
};
|
|
1825
|
+
function requiredScope(req) {
|
|
1826
|
+
const write = !REST_SAFE_METHODS.includes((req.method ?? "GET").toUpperCase());
|
|
1827
|
+
if (req.path.startsWith("/dns/")) return write ? "dns_write" : "dns_read";
|
|
1828
|
+
if (req.path === "/domains" || /^\/domains\/[^/]+(?:\/forwarding)?$/.test(req.path))
|
|
1829
|
+
return write ? "domains_write" : "domains_read";
|
|
1830
|
+
return void 0;
|
|
1831
|
+
}
|
|
1832
|
+
async function errorDetail(response) {
|
|
1833
|
+
try {
|
|
1834
|
+
const body = await response.json();
|
|
1835
|
+
if (typeof body?.code !== "string" || typeof body.message !== "string" || !body.message)
|
|
1836
|
+
return null;
|
|
1837
|
+
return { code: body.code, message: body.message.replace(/\s+/g, " ").slice(0, 200) };
|
|
1838
|
+
} catch {
|
|
1839
|
+
return null;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
var Domain101Registrar = class extends BaseRegistrar {
|
|
1843
|
+
name = "101domain";
|
|
1844
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
1845
|
+
static displayName = "101domain";
|
|
1846
|
+
static website = "101domain.com";
|
|
1847
|
+
static supportsSandbox = false;
|
|
1848
|
+
static configFields = [
|
|
1849
|
+
{ name: "apiKey", label: "API key", type: "password", required: true }
|
|
1850
|
+
];
|
|
1851
|
+
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.";
|
|
1852
|
+
static extendedFeatures = [
|
|
1853
|
+
Feature.GetDomainForwarding,
|
|
1854
|
+
Feature.SetDomainForwarding
|
|
1855
|
+
];
|
|
1856
|
+
constructor(credentials, options) {
|
|
1857
|
+
const apiKey = credentials.apiKey?.trim() ?? "";
|
|
1858
|
+
if (/[\r\n]/.test(apiKey)) throw new ConfigurationError("101domain: invalid API key format");
|
|
1859
|
+
const config = {
|
|
1860
|
+
baseUrl: selectBaseUrl("101domain", options?.environment, {
|
|
1861
|
+
production: "https://api.101domain.com/v1"
|
|
1862
|
+
}),
|
|
1863
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1864
|
+
};
|
|
1865
|
+
super(credentials, config, options);
|
|
1866
|
+
this.http = new Domain101HttpClient({
|
|
1867
|
+
...config,
|
|
1868
|
+
safeMethods: REST_SAFE_METHODS,
|
|
1869
|
+
options: this.options
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
async testConnection(opts) {
|
|
1873
|
+
try {
|
|
1874
|
+
await this.request({
|
|
1875
|
+
path: "/domains",
|
|
1876
|
+
query: { page: 1, per_page: 1 },
|
|
1877
|
+
...opts
|
|
1878
|
+
});
|
|
1879
|
+
return { success: true, message: "Connection successful" };
|
|
1880
|
+
} catch (error) {
|
|
1881
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
async listDomains(opts) {
|
|
1885
|
+
const { search, detailed: _detailed, ...requestOpts } = opts ?? {};
|
|
1886
|
+
const result = [];
|
|
1887
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1888
|
+
for (let page = 1; ; page++) {
|
|
1889
|
+
const response = await this.request({
|
|
1890
|
+
path: "/domains",
|
|
1891
|
+
query: { page, per_page: 50 },
|
|
1892
|
+
...requestOpts
|
|
1893
|
+
});
|
|
1894
|
+
if (!Array.isArray(response.data)) throw invalid("domain list");
|
|
1895
|
+
const pagination = response.meta?.pagination;
|
|
1896
|
+
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)
|
|
1897
|
+
throw invalid("pagination");
|
|
1898
|
+
for (const raw of response.data) {
|
|
1899
|
+
const domain = toDomain(raw);
|
|
1900
|
+
if (seen.has(domain.domainName)) throw invalid("duplicate domain across pages");
|
|
1901
|
+
seen.add(domain.domainName);
|
|
1902
|
+
result.push(domain);
|
|
1903
|
+
}
|
|
1904
|
+
if (page >= pagination.total_pages) {
|
|
1905
|
+
if (result.length !== pagination.total) throw invalid("incomplete portfolio");
|
|
1906
|
+
return filterDomains(
|
|
1907
|
+
result.filter((d) => !d.deleted),
|
|
1908
|
+
search
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
if (!response.data.length) throw invalid("empty intermediate page");
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
async getDomain(domainName, opts) {
|
|
1915
|
+
const domain = toDomain(
|
|
1916
|
+
(await this.request({ path: domainPath(domainName), ...opts })).data
|
|
1917
|
+
);
|
|
1918
|
+
if (domain.domainName !== normalizeDomain(domainName)) throw invalid("domain identity");
|
|
1919
|
+
return domain;
|
|
1920
|
+
}
|
|
1921
|
+
async getNameservers(domainName, opts) {
|
|
1922
|
+
const raw = (await this.request({ path: `${dnsPath(domainName)}/nameservers`, ...opts })).data;
|
|
1923
|
+
if (!Array.isArray(raw) || raw.some((n) => typeof n !== "string")) throw invalid("nameservers");
|
|
1924
|
+
return raw.map((n) => n.toLowerCase());
|
|
1925
|
+
}
|
|
1926
|
+
async updateNameservers(domainName, nameservers, opts) {
|
|
1927
|
+
const normalized = nameservers.map((n) => n.trim().toLowerCase());
|
|
1928
|
+
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)))
|
|
1929
|
+
throw new ConfigurationError("101domain: provide 2\u201313 distinct nameserver hostnames");
|
|
1930
|
+
try {
|
|
1931
|
+
const response = await this.request({
|
|
1932
|
+
...opts,
|
|
1933
|
+
method: "PUT",
|
|
1934
|
+
path: `${dnsPath(domainName)}/nameservers`,
|
|
1935
|
+
body: { nameservers: normalized }
|
|
1936
|
+
});
|
|
1937
|
+
if (response.data?.change_status === "pending")
|
|
1938
|
+
return {
|
|
1939
|
+
success: true,
|
|
1940
|
+
pending: true,
|
|
1941
|
+
message: "Nameserver change submitted; pending registry processing. Re-read nameservers to confirm completion."
|
|
1942
|
+
};
|
|
1943
|
+
if (response.data?.change_status === "failed")
|
|
1944
|
+
return { success: false, message: "101domain rejected the nameserver change" };
|
|
1945
|
+
if (response.data?.change_status !== "completed")
|
|
1946
|
+
throw new OutcomeUnknownError(
|
|
1947
|
+
"101domain: invalid nameserver change confirmation. Re-read nameservers before trying again."
|
|
1948
|
+
);
|
|
1949
|
+
return { success: true, message: "Nameservers already configured as requested" };
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
return failed(error);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
async checkAvailability(domainNames, opts) {
|
|
1955
|
+
const names = [...new Set(domainNames.map(normalizeDomain))];
|
|
1956
|
+
const result = [];
|
|
1957
|
+
for (const domains of chunks(names, 50)) {
|
|
1958
|
+
const response = await this.request({
|
|
1959
|
+
...opts,
|
|
1960
|
+
method: "POST",
|
|
1961
|
+
path: "/domains/bulk-search",
|
|
1962
|
+
body: { domains, pricing_term: 1 }
|
|
1963
|
+
});
|
|
1964
|
+
if (!Array.isArray(response.data) || (response.invalid?.length ?? 0) > 0)
|
|
1965
|
+
throw invalid("availability results");
|
|
1966
|
+
const found = /* @__PURE__ */ new Set();
|
|
1967
|
+
for (const raw of response.data) {
|
|
1968
|
+
if (typeof raw.domain_name !== "string" || typeof raw.available !== "boolean")
|
|
1969
|
+
throw invalid("availability result");
|
|
1970
|
+
const domainName = normalizeDomain(raw.domain_name);
|
|
1971
|
+
if (!domains.includes(domainName) || found.has(domainName))
|
|
1972
|
+
throw invalid("availability identity");
|
|
1973
|
+
found.add(domainName);
|
|
1974
|
+
const price2 = oneYear(raw.pricing);
|
|
1975
|
+
result.push({
|
|
1976
|
+
domainName,
|
|
1977
|
+
available: raw.available,
|
|
1978
|
+
premium: price2?.premium,
|
|
1979
|
+
price: amount(price2?.register),
|
|
1980
|
+
renewalPrice: amount(price2?.renew),
|
|
1981
|
+
currency: price2?.currency,
|
|
1982
|
+
period: 1
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
if (found.size !== domains.length) throw invalid("incomplete availability results");
|
|
1986
|
+
}
|
|
1987
|
+
return result;
|
|
1988
|
+
}
|
|
1989
|
+
async getPricing(tldOrDomain, opts) {
|
|
1990
|
+
const name = normalizeDomain(tldOrDomain);
|
|
1991
|
+
if (name.startsWith(".") || !name.includes(".")) {
|
|
1992
|
+
const tld = name.replace(/^\./, "");
|
|
1993
|
+
const response2 = await this.request({
|
|
1994
|
+
path: `/tlds/${encodeURIComponent("." + tld)}`,
|
|
1995
|
+
query: { pricing_terms: "1" },
|
|
1996
|
+
...opts
|
|
1997
|
+
});
|
|
1998
|
+
const price3 = oneYear(response2.data?.pricing);
|
|
1999
|
+
if (!price3) throw new NotFoundError("101domain: no one-year pricing for this TLD");
|
|
2000
|
+
return {
|
|
2001
|
+
tld,
|
|
2002
|
+
currency: price3.currency,
|
|
2003
|
+
registration: amount(price3.register),
|
|
2004
|
+
renewal: amount(price3.renew),
|
|
2005
|
+
transfer: amount(price3.transfer)
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
const response = await this.request({
|
|
2009
|
+
path: "/domains/search",
|
|
2010
|
+
query: { domain_name: name, pricing_terms: "1" },
|
|
2011
|
+
...opts
|
|
2012
|
+
});
|
|
2013
|
+
const raw = response.data;
|
|
2014
|
+
if (!raw || normalizeDomain(raw.domain_name) !== name) throw invalid("pricing identity");
|
|
2015
|
+
const price2 = oneYear(raw.pricing);
|
|
2016
|
+
if (!price2) throw new NotFoundError("101domain: pricing unavailable for this domain");
|
|
2017
|
+
return {
|
|
2018
|
+
tld: name.slice(name.indexOf(".") + 1),
|
|
2019
|
+
currency: price2.currency,
|
|
2020
|
+
registration: amount(price2.register),
|
|
2021
|
+
renewal: amount(price2.renew),
|
|
2022
|
+
transfer: amount(price2.transfer)
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
async getDnsRecords(domainName, opts) {
|
|
2026
|
+
return (await this.rawRecords(domainName, opts)).map(fromRecord);
|
|
2027
|
+
}
|
|
2028
|
+
// Reconcile by content-derived IDs. Preserve unchanged records, edit by ID, create additions
|
|
2029
|
+
// before removing stale records, and stop on any failed batch. Never retry an
|
|
2030
|
+
// uncertain write. This replaces the custom records, not the apex NS delegation.
|
|
2031
|
+
async setDnsRecords(domainName, records, opts) {
|
|
2032
|
+
const desired = records.map((r) => recordSpec(r, domainName));
|
|
2033
|
+
const unique = desired.filter(
|
|
2034
|
+
(r, i) => desired.findIndex((other) => recordSignature(other) === recordSignature(r)) === i
|
|
2035
|
+
);
|
|
2036
|
+
let changed = false;
|
|
2037
|
+
try {
|
|
2038
|
+
const stale = (await this.rawRecords(domainName, opts)).map((raw) => ({ raw, spec: recordSpec(fromRecord(raw), domainName, false) })).filter(({ spec }) => !(spec.type === "NS" && spec.name === ""));
|
|
2039
|
+
const additions = [];
|
|
2040
|
+
const edits = [];
|
|
2041
|
+
for (const record of unique) {
|
|
2042
|
+
const index = stale.findIndex(
|
|
2043
|
+
({ spec }) => recordSignature(spec) === recordSignature(record)
|
|
2044
|
+
);
|
|
2045
|
+
if (index >= 0) stale.splice(index, 1);
|
|
2046
|
+
else {
|
|
2047
|
+
const priorIndex = stale.findIndex(
|
|
2048
|
+
({ spec }) => spec.type === record.type && spec.name === record.name
|
|
2049
|
+
);
|
|
2050
|
+
const prior = priorIndex >= 0 ? stale.splice(priorIndex, 1)[0].raw : void 0;
|
|
2051
|
+
if (prior?.proxied) record.proxied = true;
|
|
2052
|
+
if (record.ttl === 1 && record.proxied !== true)
|
|
2053
|
+
throw new ConfigurationError(
|
|
2054
|
+
"101domain: automatic TTL requires an existing proxied SWA record"
|
|
2055
|
+
);
|
|
2056
|
+
if (prior) {
|
|
2057
|
+
const edit = { ...record, id: prior.id };
|
|
2058
|
+
delete edit.name;
|
|
2059
|
+
delete edit.type;
|
|
2060
|
+
edits.push(edit);
|
|
2061
|
+
} else additions.push(record);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
for (const batch of chunks(edits, 25)) {
|
|
2065
|
+
const response = await this.request({
|
|
2066
|
+
...opts,
|
|
2067
|
+
method: "PATCH",
|
|
2068
|
+
path: `${dnsPath(domainName)}/records`,
|
|
2069
|
+
body: { records: batch }
|
|
2070
|
+
});
|
|
2071
|
+
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(
|
|
2072
|
+
(r) => !r || !batch.some((edit) => edit.id === r.old_id) || typeof r.id !== "string" || !r.id
|
|
2073
|
+
))
|
|
2074
|
+
throw new OutcomeUnknownError(
|
|
2075
|
+
"101domain: invalid DNS edit confirmation. Re-read the zone before trying again."
|
|
2076
|
+
);
|
|
2077
|
+
changed = true;
|
|
2078
|
+
}
|
|
2079
|
+
for (const batch of chunks(additions, 25)) {
|
|
2080
|
+
await this.request({
|
|
2081
|
+
...opts,
|
|
2082
|
+
method: "POST",
|
|
2083
|
+
path: `${dnsPath(domainName)}/records`,
|
|
2084
|
+
body: { records: batch }
|
|
2085
|
+
});
|
|
2086
|
+
changed = true;
|
|
2087
|
+
}
|
|
2088
|
+
for (const batch of chunks(stale, 25)) {
|
|
2089
|
+
await this.request({
|
|
2090
|
+
...opts,
|
|
2091
|
+
method: "DELETE",
|
|
2092
|
+
path: `${dnsPath(domainName)}/records`,
|
|
2093
|
+
body: { ids: batch.map(({ raw }) => raw.id) }
|
|
2094
|
+
});
|
|
2095
|
+
changed = true;
|
|
2096
|
+
}
|
|
2097
|
+
return { success: true, message: "DNS records updated successfully" };
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
const result = failed(error);
|
|
2100
|
+
if (!changed && !result.outcome) return result;
|
|
2101
|
+
return {
|
|
2102
|
+
...result,
|
|
2103
|
+
message: `${result.message}. DNS replacement is not atomic; read the zone before retrying.`
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
async getDomainForwarding(domainName, opts) {
|
|
2108
|
+
const raw = (await this.request({
|
|
2109
|
+
path: domainPath(domainName),
|
|
2110
|
+
...opts
|
|
2111
|
+
})).data;
|
|
2112
|
+
if (!raw || normalizeDomain(raw.domain_name) !== normalizeDomain(domainName))
|
|
2113
|
+
throw invalid("domain identity");
|
|
2114
|
+
const forward = raw.web_forwarding;
|
|
2115
|
+
if (forward === null || forward?.destination === null && forward.type === null) return [];
|
|
2116
|
+
if (!forward || typeof forward.destination !== "string" || forward.type !== "301" && forward.type !== "cloak")
|
|
2117
|
+
throw invalid("forwarding");
|
|
2118
|
+
return [
|
|
2119
|
+
{
|
|
2120
|
+
host: "@",
|
|
2121
|
+
url: forward.destination,
|
|
2122
|
+
type: forward.type === "301" ? "permanent" : "masked"
|
|
2123
|
+
}
|
|
2124
|
+
];
|
|
2125
|
+
}
|
|
2126
|
+
async setDomainForwarding(domainName, forwards, opts) {
|
|
2127
|
+
if (forwards.length > 1 || forwards.some((f) => f.host !== "@" || f.type !== "permanent"))
|
|
2128
|
+
throw new ConfigurationError(
|
|
2129
|
+
"101domain: only one permanent apex forwarding rule is supported"
|
|
2130
|
+
);
|
|
2131
|
+
if (forwards.length) {
|
|
2132
|
+
let url;
|
|
2133
|
+
try {
|
|
2134
|
+
url = new URL(forwards[0].url);
|
|
2135
|
+
} catch {
|
|
2136
|
+
throw new ConfigurationError("101domain: forwarding needs a valid HTTP(S) URL");
|
|
2137
|
+
}
|
|
2138
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
|
|
2139
|
+
throw new ConfigurationError("101domain: forwarding needs a valid HTTP(S) URL");
|
|
2140
|
+
}
|
|
2141
|
+
try {
|
|
2142
|
+
const existing = await this.getDomainForwarding(domainName, opts);
|
|
2143
|
+
const path = `${domainPath(domainName)}/forwarding`;
|
|
2144
|
+
if (!forwards.length) {
|
|
2145
|
+
if (existing.length) await this.request({ ...opts, method: "DELETE", path });
|
|
2146
|
+
} else if (existing[0]?.url !== forwards[0].url || existing[0]?.type !== "permanent") {
|
|
2147
|
+
await this.request({
|
|
2148
|
+
...opts,
|
|
2149
|
+
method: existing.length ? "PATCH" : "POST",
|
|
2150
|
+
path,
|
|
2151
|
+
body: { destination: forwards[0].url, type: "301" }
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
return {
|
|
2155
|
+
success: true,
|
|
2156
|
+
message: forwards.length ? "URL forwarding updated" : "URL forwarding cleared"
|
|
2157
|
+
};
|
|
2158
|
+
} catch (error) {
|
|
2159
|
+
return failed(error);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
async request(req) {
|
|
2163
|
+
const raw = await this.http.request(req);
|
|
2164
|
+
if (!raw || typeof raw !== "object" || raw.status !== "success" || !("data" in raw)) {
|
|
2165
|
+
if (req.call?.intent === "write" && !REST_SAFE_METHODS.includes(req.method ?? "GET"))
|
|
2166
|
+
throw new OutcomeUnknownError(
|
|
2167
|
+
"101domain: invalid write confirmation. Re-read the domain before trying again."
|
|
2168
|
+
);
|
|
2169
|
+
throw invalid("response envelope");
|
|
2170
|
+
}
|
|
2171
|
+
return raw;
|
|
2172
|
+
}
|
|
2173
|
+
async rawRecords(domainName, opts) {
|
|
2174
|
+
const raw = (await this.request({ path: `${dnsPath(domainName)}/records`, ...opts })).data;
|
|
2175
|
+
if (!Array.isArray(raw) || raw.some(
|
|
2176
|
+
(r) => !r || typeof r.id !== "string" || !r.id || typeof r.name !== "string" || typeof r.value !== "string" || typeof r.type !== "string" || !Number.isInteger(r.ttl)
|
|
2177
|
+
))
|
|
2178
|
+
throw invalid("DNS records");
|
|
2179
|
+
if (new Set(raw.map((r) => r.id)).size !== raw.length) throw invalid("duplicate DNS ids");
|
|
2180
|
+
return raw;
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
function invalid(part) {
|
|
2184
|
+
return new InvalidResponseError(`101domain: invalid ${part}`);
|
|
2185
|
+
}
|
|
2186
|
+
function failed(error) {
|
|
2187
|
+
return {
|
|
2188
|
+
success: false,
|
|
2189
|
+
message: toRegistrarError(error).message,
|
|
2190
|
+
...error instanceof OutcomeUnknownError ? { outcome: "unknown" } : {}
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function domainPath(name) {
|
|
2194
|
+
return `/domains/${encodeURIComponent(normalizeDomain(name))}`;
|
|
2195
|
+
}
|
|
2196
|
+
function dnsPath(name) {
|
|
2197
|
+
return `/dns/${encodeURIComponent(normalizeDomain(name))}`;
|
|
2198
|
+
}
|
|
2199
|
+
function chunks(items, size) {
|
|
2200
|
+
const result = [];
|
|
2201
|
+
for (let i = 0; i < items.length; i += size) result.push(items.slice(i, i + size));
|
|
2202
|
+
return result;
|
|
2203
|
+
}
|
|
2204
|
+
function oneYear(pricing) {
|
|
2205
|
+
if (pricing == null) return void 0;
|
|
2206
|
+
if (!Array.isArray(pricing)) throw invalid("pricing");
|
|
2207
|
+
return pricing.find((p) => p?.term_years === 1);
|
|
2208
|
+
}
|
|
2209
|
+
function amount(raw) {
|
|
2210
|
+
if (raw == null) return void 0;
|
|
2211
|
+
if (!/^\d+(?:\.\d+)?$/.test(raw)) throw invalid("price");
|
|
2212
|
+
const value = Number(raw);
|
|
2213
|
+
if (!Number.isFinite(value)) throw invalid("price");
|
|
2214
|
+
return value;
|
|
2215
|
+
}
|
|
2216
|
+
function toDomain(raw) {
|
|
2217
|
+
if (!raw || typeof raw.domain_name !== "string" || !raw.domain_name.trim())
|
|
2218
|
+
throw invalid("domain");
|
|
2219
|
+
return createDomain({
|
|
2220
|
+
domainName: normalizeDomain(raw.domain_name),
|
|
2221
|
+
registrar: "101domain",
|
|
2222
|
+
status: raw.status,
|
|
2223
|
+
createdDate: raw.registered_at,
|
|
2224
|
+
expirationDate: raw.expires_at,
|
|
2225
|
+
autoRenew: raw.auto_renew,
|
|
2226
|
+
locked: raw.registry_statuses?.some((s) => /^clientTransferProhibited$/i.test(s)),
|
|
2227
|
+
nameservers: raw.nameservers,
|
|
2228
|
+
deleted: !isHeld(raw)
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
var GONE = /* @__PURE__ */ new Set(["DELETED", "CANCELLED", "DENIED", "XFER_AWAY"]);
|
|
2232
|
+
function isHeld(raw) {
|
|
2233
|
+
const status = raw.status?.toUpperCase() ?? "";
|
|
2234
|
+
return !GONE.has(status) && !status.startsWith("XFER_IN_") && raw.registered_at != null;
|
|
2235
|
+
}
|
|
2236
|
+
function fromRecord(raw) {
|
|
2237
|
+
const common = { name: raw.name || "@", type: raw.type, ttl: raw.ttl };
|
|
2238
|
+
if (raw.type === "MX") {
|
|
2239
|
+
const match = /^(\d+)\s+(.+)$/.exec(raw.value);
|
|
2240
|
+
if (!match) throw invalid("MX record");
|
|
2241
|
+
return { ...common, value: match[2], priority: Number(match[1]) };
|
|
2242
|
+
}
|
|
2243
|
+
if (raw.type === "SRV") {
|
|
2244
|
+
const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/.exec(raw.value);
|
|
2245
|
+
if (!match) throw invalid("SRV record");
|
|
2246
|
+
return {
|
|
2247
|
+
...common,
|
|
2248
|
+
value: match[4],
|
|
2249
|
+
priority: Number(match[1]),
|
|
2250
|
+
weight: Number(match[2]),
|
|
2251
|
+
port: Number(match[3])
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
return { ...common, value: raw.value };
|
|
2255
|
+
}
|
|
2256
|
+
function recordSpec(record, domain, strict = true) {
|
|
2257
|
+
const type = record.type.toUpperCase();
|
|
2258
|
+
const zone = normalizeDomain(domain);
|
|
2259
|
+
let name = record.name.trim().toLowerCase().replace(/\.$/, "");
|
|
2260
|
+
if (name === zone || name === "@") name = "";
|
|
2261
|
+
else if (name.endsWith("." + zone)) name = name.slice(0, -zone.length - 1);
|
|
2262
|
+
const reject = (message) => {
|
|
2263
|
+
if (strict) throw new ConfigurationError(`101domain: ${message}`);
|
|
2264
|
+
};
|
|
2265
|
+
if (!["A", "AAAA", "CNAME", "NS", "MX", "TXT", "SRV", "CAA"].includes(type) || type === "NS" && !name)
|
|
2266
|
+
reject("unsupported DNS type or apex NS record");
|
|
2267
|
+
const ttl = record.ttl ?? 3600;
|
|
2268
|
+
if (!Number.isInteger(ttl) || ttl !== 1 && ttl < 300)
|
|
2269
|
+
reject("DNS TTL must be at least 300 seconds");
|
|
2270
|
+
const result = { name, type, ttl };
|
|
2271
|
+
if (type === "MX" || type === "SRV") {
|
|
2272
|
+
if (!Number.isInteger(record.priority) || record.priority < 0 || record.priority > 65535)
|
|
2273
|
+
reject("MX/SRV priority is required");
|
|
2274
|
+
result.priority = record.priority;
|
|
2275
|
+
result.target = record.value;
|
|
2276
|
+
if (type === "SRV") {
|
|
2277
|
+
if (![record.weight, record.port].every((n) => Number.isInteger(n) && n >= 0 && n <= 65535))
|
|
2278
|
+
reject("SRV weight and port are required");
|
|
2279
|
+
result.weight = record.weight;
|
|
2280
|
+
result.port = record.port;
|
|
2281
|
+
}
|
|
2282
|
+
} else if (type === "CAA") {
|
|
2283
|
+
const match = /^(\d+)\s+(\w+)\s+"([\s\S]*)"$/.exec(record.value);
|
|
2284
|
+
if (!match || Number(match[1]) > 255) {
|
|
2285
|
+
reject("CAA needs flags, tag and quoted value");
|
|
2286
|
+
result.value = record.value;
|
|
2287
|
+
} else {
|
|
2288
|
+
result.flag = Number(match[1]);
|
|
2289
|
+
result.tag = match[2];
|
|
2290
|
+
result.value = match[3];
|
|
2291
|
+
}
|
|
2292
|
+
} else result.value = record.value;
|
|
2293
|
+
return result;
|
|
2294
|
+
}
|
|
2295
|
+
function recordSignature(record) {
|
|
2296
|
+
return JSON.stringify(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
2297
|
+
}
|
|
2298
|
+
|
|
1778
2299
|
// src/registrars/dynadot.ts
|
|
1779
2300
|
var DYNADOT_DEFAULT_NS = ["ns1.dynadot.com", "ns2.dynadot.com"];
|
|
1780
2301
|
var SEARCH_BATCH_SIZE = 100;
|
|
@@ -2452,12 +2973,12 @@ function isPrivacyOn(privacy) {
|
|
|
2452
2973
|
}
|
|
2453
2974
|
function oneYearPrice(prices) {
|
|
2454
2975
|
if (!prices?.length) return void 0;
|
|
2455
|
-
const
|
|
2976
|
+
const oneYear2 = prices.find((p) => /\b1\s*year\b/i.test(p.unit ?? "")) ?? prices[0];
|
|
2456
2977
|
return {
|
|
2457
|
-
currency:
|
|
2458
|
-
registration: toNumber(
|
|
2459
|
-
renewal: toNumber(
|
|
2460
|
-
transfer: toNumber(
|
|
2978
|
+
currency: oneYear2.currency,
|
|
2979
|
+
registration: toNumber(oneYear2.registration_price),
|
|
2980
|
+
renewal: toNumber(oneYear2.renewal_price),
|
|
2981
|
+
transfer: toNumber(oneYear2.transfer_price)
|
|
2461
2982
|
};
|
|
2462
2983
|
}
|
|
2463
2984
|
function extractNameservers(glue) {
|
|
@@ -2747,8 +3268,8 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
2747
3268
|
available: status.startsWith("available"),
|
|
2748
3269
|
premium: status.includes("premium") || price2?.type === "premium"
|
|
2749
3270
|
};
|
|
2750
|
-
const
|
|
2751
|
-
if (
|
|
3271
|
+
const amount2 = pickPrice(price2);
|
|
3272
|
+
if (amount2 != null) result.price = amount2;
|
|
2752
3273
|
if (resp.currency) result.currency = resp.currency;
|
|
2753
3274
|
if (price2?.duration_unit === "y" && price2.min_duration != null) {
|
|
2754
3275
|
result.period = price2.min_duration;
|
|
@@ -5261,7 +5782,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5261
5782
|
}
|
|
5262
5783
|
async getAuthCode(domainName, opts) {
|
|
5263
5784
|
const result = await this.http.request({
|
|
5264
|
-
path: `${
|
|
5785
|
+
path: `${domainPath2(domainName)}:getAuthCode`,
|
|
5265
5786
|
...opts
|
|
5266
5787
|
});
|
|
5267
5788
|
if (!result?.authCode) throw new InvalidResponseError("namecom: missing authorization code");
|
|
@@ -5356,7 +5877,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5356
5877
|
validYears(years);
|
|
5357
5878
|
const pricing = await this.domainPricing(domainName, years, opts);
|
|
5358
5879
|
return this.purchase(
|
|
5359
|
-
`${
|
|
5880
|
+
`${domainPath2(domainName)}:renew`,
|
|
5360
5881
|
{
|
|
5361
5882
|
years,
|
|
5362
5883
|
purchasePrice: purchaseQuote(pricing, "renewalPrice")
|
|
@@ -5401,7 +5922,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5401
5922
|
}
|
|
5402
5923
|
updateNameservers(domainName, nameservers, opts) {
|
|
5403
5924
|
return this.mutate(
|
|
5404
|
-
{ method: "POST", path: `${
|
|
5925
|
+
{ method: "POST", path: `${domainPath2(domainName)}:setNameservers`, body: { nameservers } },
|
|
5405
5926
|
opts
|
|
5406
5927
|
);
|
|
5407
5928
|
}
|
|
@@ -5432,14 +5953,14 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5432
5953
|
return this.mutate(
|
|
5433
5954
|
{
|
|
5434
5955
|
method: "POST",
|
|
5435
|
-
path: `${
|
|
5956
|
+
path: `${domainPath2(domainName)}:setContacts`,
|
|
5436
5957
|
body: { contacts: toContacts(contacts) }
|
|
5437
5958
|
},
|
|
5438
5959
|
opts
|
|
5439
5960
|
);
|
|
5440
5961
|
}
|
|
5441
5962
|
async getDnsRecords(domainName, opts) {
|
|
5442
|
-
return (await this.pages(`${
|
|
5963
|
+
return (await this.pages(`${domainPath2(domainName)}/records`, "records", opts)).map(fromRecord2);
|
|
5443
5964
|
}
|
|
5444
5965
|
// No bulk replace endpoint. Reconcile by id: preserve identical records, update
|
|
5445
5966
|
// existing type/host pairs, create additions, then delete stale records. This
|
|
@@ -5447,7 +5968,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5447
5968
|
async setDnsRecords(domainName, records, opts) {
|
|
5448
5969
|
const desired = records.map(toRecord).filter((record, index, all) => all.findIndex((other) => sameRecord(other, record)) === index);
|
|
5449
5970
|
try {
|
|
5450
|
-
const path = `${
|
|
5971
|
+
const path = `${domainPath2(domainName)}/records`;
|
|
5451
5972
|
const existing = await this.pages(path, "records", opts);
|
|
5452
5973
|
if (existing.some((r) => !Number.isInteger(r.id) || r.id <= 0))
|
|
5453
5974
|
throw new InvalidResponseError("namecom: DNS response missing record ids");
|
|
@@ -5484,14 +6005,14 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5484
6005
|
}
|
|
5485
6006
|
}
|
|
5486
6007
|
patchDomain(domainName, body, opts) {
|
|
5487
|
-
return this.mutate({ method: "PATCH", path:
|
|
6008
|
+
return this.mutate({ method: "PATCH", path: domainPath2(domainName), body }, opts);
|
|
5488
6009
|
}
|
|
5489
6010
|
rawDomain(domainName, opts) {
|
|
5490
|
-
return this.http.request({ path:
|
|
6011
|
+
return this.http.request({ path: domainPath2(domainName), ...opts });
|
|
5491
6012
|
}
|
|
5492
6013
|
domainPricing(domainName, years, opts) {
|
|
5493
6014
|
return this.http.request({
|
|
5494
|
-
path: `${
|
|
6015
|
+
path: `${domainPath2(domainName)}:getPricing`,
|
|
5495
6016
|
query: { years },
|
|
5496
6017
|
...opts
|
|
5497
6018
|
});
|
|
@@ -5607,7 +6128,7 @@ function toContacts(contacts) {
|
|
|
5607
6128
|
throw new ConfigurationError("namecom: at least one contact is required");
|
|
5608
6129
|
return result;
|
|
5609
6130
|
}
|
|
5610
|
-
function
|
|
6131
|
+
function domainPath2(domainName) {
|
|
5611
6132
|
return `/domains/${encodeURIComponent(normalizeDomain(domainName))}`;
|
|
5612
6133
|
}
|
|
5613
6134
|
function validYears(years) {
|
|
@@ -5647,7 +6168,7 @@ function toRecord(r) {
|
|
|
5647
6168
|
...["MX", "SRV"].includes(type) ? { priority: r.priority ?? 0 } : {}
|
|
5648
6169
|
};
|
|
5649
6170
|
}
|
|
5650
|
-
function
|
|
6171
|
+
function fromRecord2(r) {
|
|
5651
6172
|
const type = r.type.toUpperCase();
|
|
5652
6173
|
const record = { type, name: r.host || "@", value: r.answer, ttl: r.ttl };
|
|
5653
6174
|
if (type === "MX" || type === "SRV") record.priority = r.priority ?? 0;
|
|
@@ -7468,16 +7989,17 @@ function toSpaceshipContact(c) {
|
|
|
7468
7989
|
|
|
7469
7990
|
// src/registrars/index.ts
|
|
7470
7991
|
var registrars = {
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7992
|
+
"101domain": Domain101Registrar,
|
|
7993
|
+
"cloudflare": CloudflareRegistrar,
|
|
7994
|
+
"dynadot": DynadotRegistrar,
|
|
7995
|
+
"gandi": GandiRegistrar,
|
|
7996
|
+
"godaddy": GoDaddyRegistrar,
|
|
7997
|
+
"namebright": NameBrightRegistrar,
|
|
7998
|
+
"namecheap": NamecheapRegistrar,
|
|
7999
|
+
"namecom": NamecomRegistrar,
|
|
8000
|
+
"namesilo": NameSiloRegistrar,
|
|
8001
|
+
"porkbun": PorkbunRegistrar,
|
|
8002
|
+
"spaceship": SpaceshipRegistrar
|
|
7481
8003
|
};
|
|
7482
8004
|
function createRegistrar(name, credentials, options) {
|
|
7483
8005
|
const RegistrarClass = registrars[name];
|
|
@@ -7495,6 +8017,7 @@ exports.ConfigurationError = ConfigurationError;
|
|
|
7495
8017
|
exports.ConnectionError = ConnectionError;
|
|
7496
8018
|
exports.ConsentRequiredError = ConsentRequiredError;
|
|
7497
8019
|
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
8020
|
+
exports.Domain101Registrar = Domain101Registrar;
|
|
7498
8021
|
exports.DynadotRegistrar = DynadotRegistrar;
|
|
7499
8022
|
exports.EXTENDED_FEATURES = EXTENDED_FEATURES;
|
|
7500
8023
|
exports.FEATURE_CALLS = FEATURE_CALLS;
|