@aoxborrow/registrar-client 0.5.0 → 0.7.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 +181 -33
- package/dist/index.cjs +742 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -3
- package/dist/index.d.ts +85 -3
- package/dist/index.js +736 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +69 -1
- package/src/features.ts +42 -0
- package/src/http.ts +76 -5
- package/src/index.ts +10 -2
- package/src/registrar.ts +47 -2
- package/src/registrars/cloudflare.ts +3 -0
- package/src/registrars/dynadot.ts +3 -0
- package/src/registrars/gandi.ts +3 -0
- package/src/registrars/godaddy.ts +3 -0
- package/src/registrars/index.ts +2 -0
- package/src/registrars/namebright.ts +6 -2
- package/src/registrars/namecheap.ts +6 -1
- package/src/registrars/namecom.ts +708 -0
- package/src/registrars/namesilo.ts +4 -4
- package/src/registrars/porkbun.ts +3 -3
- package/src/registrars/spaceship.ts +3 -0
- package/src/types.ts +30 -1
- package/src/utils.ts +16 -1
package/dist/index.js
CHANGED
|
@@ -55,11 +55,61 @@ var TimeoutError = class extends RetryableRegistrarError {
|
|
|
55
55
|
var ConnectionError = class extends RetryableRegistrarError {
|
|
56
56
|
status = 503;
|
|
57
57
|
// Service Unavailable
|
|
58
|
-
|
|
58
|
+
// true when the failure is known to have happened before the request left:
|
|
59
|
+
// DNS, connection refused, TLS handshake, or a proxy that never opened its
|
|
60
|
+
// tunnel. The registrar cannot have acted on it, so it is safe to re-send
|
|
61
|
+
// even for a write.
|
|
62
|
+
notSent;
|
|
63
|
+
constructor(message, details = {}) {
|
|
59
64
|
super(message);
|
|
60
65
|
this.name = "ConnectionError";
|
|
66
|
+
this.notSent = details.notSent ?? false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var OutcomeUnknownError = class extends RegistrarError {
|
|
70
|
+
status = 504;
|
|
71
|
+
// Gateway Timeout
|
|
72
|
+
feature;
|
|
73
|
+
cause;
|
|
74
|
+
constructor(message, details = {}) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = "OutcomeUnknownError";
|
|
77
|
+
this.feature = details.feature;
|
|
78
|
+
this.cause = details.cause;
|
|
61
79
|
}
|
|
62
80
|
};
|
|
81
|
+
var NOT_SENT = /* @__PURE__ */ Symbol.for("registrar-client.notSent");
|
|
82
|
+
function markNotSent(error) {
|
|
83
|
+
if (typeof error === "object" && error !== null) {
|
|
84
|
+
Object.defineProperty(error, NOT_SENT, { value: true, enumerable: false });
|
|
85
|
+
}
|
|
86
|
+
return error;
|
|
87
|
+
}
|
|
88
|
+
var PRE_SEND_CODES = /* @__PURE__ */ new Set([
|
|
89
|
+
"ENOTFOUND",
|
|
90
|
+
"EAI_AGAIN",
|
|
91
|
+
"ECONNREFUSED",
|
|
92
|
+
"EHOSTUNREACH",
|
|
93
|
+
"ENETUNREACH",
|
|
94
|
+
"EADDRNOTAVAIL",
|
|
95
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
96
|
+
"CERT_HAS_EXPIRED",
|
|
97
|
+
"CERT_NOT_YET_VALID",
|
|
98
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
99
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
100
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
101
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
102
|
+
"ERR_TLS_CERT_ALTNAME_INVALID"
|
|
103
|
+
]);
|
|
104
|
+
function wasNotSent(error) {
|
|
105
|
+
for (let e = error, depth = 0; e && typeof e === "object" && depth < 5; depth++) {
|
|
106
|
+
if (e[NOT_SENT] === true) return true;
|
|
107
|
+
const code = e.code;
|
|
108
|
+
if (typeof code === "string" && PRE_SEND_CODES.has(code)) return true;
|
|
109
|
+
e = e.cause;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
63
113
|
var AuthenticationError = class extends RegistrarError {
|
|
64
114
|
status = 401;
|
|
65
115
|
// Unauthorized
|
|
@@ -226,6 +276,9 @@ function createDomain(data = {}) {
|
|
|
226
276
|
deleted: data.deleted ?? false
|
|
227
277
|
};
|
|
228
278
|
}
|
|
279
|
+
function asRead(opts) {
|
|
280
|
+
return { ...opts, call: { ...opts?.call, intent: "read" } };
|
|
281
|
+
}
|
|
229
282
|
|
|
230
283
|
// src/client.ts
|
|
231
284
|
var RegistrarClient = class {
|
|
@@ -376,6 +429,7 @@ var DEFAULT_OPTIONS = {
|
|
|
376
429
|
var USER_AGENT = "@aoxborrow/registrar-client";
|
|
377
430
|
|
|
378
431
|
// src/http.ts
|
|
432
|
+
var REST_SAFE_METHODS = ["GET", "HEAD"];
|
|
379
433
|
var HttpClient = class {
|
|
380
434
|
constructor(config) {
|
|
381
435
|
this.config = config;
|
|
@@ -399,7 +453,20 @@ var HttpClient = class {
|
|
|
399
453
|
async requestText(req) {
|
|
400
454
|
return this.withRetries(req, () => this.send(req));
|
|
401
455
|
}
|
|
402
|
-
//
|
|
456
|
+
// Shared retry-with-backoff wrapper around a single-attempt operation.
|
|
457
|
+
//
|
|
458
|
+
// What may be re-sent depends on where the failure happened and on whether
|
|
459
|
+
// the call reads or writes:
|
|
460
|
+
//
|
|
461
|
+
// failure read write
|
|
462
|
+
// never sent (DNS, refused, TLS, proxy stage) retry retry
|
|
463
|
+
// 429 retry retry
|
|
464
|
+
// unknown (timeout, dropped mid-response, 5xx) retry OutcomeUnknownError
|
|
465
|
+
// any other response fail fail
|
|
466
|
+
//
|
|
467
|
+
// A write whose outcome is unknown may have been applied, so it is never
|
|
468
|
+
// re-sent: a second renewal charges twice, a second record-create leaves a
|
|
469
|
+
// duplicate. The caller re-reads the domain instead.
|
|
403
470
|
async withRetries(req, attemptFn) {
|
|
404
471
|
const retries = req.retries ?? this.config.options.retries;
|
|
405
472
|
const backoff = req.backoff ?? this.config.options.backoff;
|
|
@@ -409,6 +476,14 @@ var HttpClient = class {
|
|
|
409
476
|
return await attemptFn();
|
|
410
477
|
} catch (error) {
|
|
411
478
|
lastError = toRegistrarError(error);
|
|
479
|
+
if (this.intentOf(req) === "write" && isOutcomeUnknown(lastError)) {
|
|
480
|
+
const unknown = new OutcomeUnknownError(
|
|
481
|
+
`The registrar did not confirm this change, so it may or may not have been applied. Check the domain before trying again. (${lastError.message})`,
|
|
482
|
+
{ feature: req.call?.feature, cause: lastError }
|
|
483
|
+
);
|
|
484
|
+
if (req.call) req.call.outcomeUnknown = unknown;
|
|
485
|
+
throw unknown;
|
|
486
|
+
}
|
|
412
487
|
if (attempt >= retries || !lastError.shouldRetry()) {
|
|
413
488
|
throw lastError;
|
|
414
489
|
}
|
|
@@ -418,6 +493,14 @@ var HttpClient = class {
|
|
|
418
493
|
}
|
|
419
494
|
throw lastError ?? new RegistrarError("Request failed");
|
|
420
495
|
}
|
|
496
|
+
// Whether a request only reads. A method the API reserves for reads wins;
|
|
497
|
+
// otherwise it is whatever the feature being served is, and a request that
|
|
498
|
+
// says nothing is assumed to write.
|
|
499
|
+
intentOf(req) {
|
|
500
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
501
|
+
if (this.config.safeMethods?.includes(method)) return "read";
|
|
502
|
+
return req.call?.intent ?? "write";
|
|
503
|
+
}
|
|
421
504
|
// build the full URL for a request, applying query parameters
|
|
422
505
|
buildUrl(req) {
|
|
423
506
|
let base;
|
|
@@ -460,7 +543,8 @@ var HttpClient = class {
|
|
|
460
543
|
try {
|
|
461
544
|
let response;
|
|
462
545
|
try {
|
|
463
|
-
|
|
546
|
+
const doFetch = req.fetch ?? this.config.options.fetch ?? fetch;
|
|
547
|
+
response = await doFetch(url, {
|
|
464
548
|
method: req.method ?? "GET",
|
|
465
549
|
headers,
|
|
466
550
|
body: req.body !== void 0 ? JSON.stringify(req.body) : void 0,
|
|
@@ -471,7 +555,9 @@ var HttpClient = class {
|
|
|
471
555
|
if (externalSignal?.aborted) throw new AbortError("Request was aborted");
|
|
472
556
|
throw new TimeoutError(`Request to '${url}' timed out after ${timeout}ms`);
|
|
473
557
|
}
|
|
474
|
-
throw new ConnectionError(`Failed to reach '${url}': ${errorMessage(error)}
|
|
558
|
+
throw new ConnectionError(`Failed to reach '${url}': ${errorMessage(error)}`, {
|
|
559
|
+
notSent: wasNotSent(error)
|
|
560
|
+
});
|
|
475
561
|
}
|
|
476
562
|
if (!response.ok) {
|
|
477
563
|
throw await this.toStatusError(response, url);
|
|
@@ -479,7 +565,17 @@ var HttpClient = class {
|
|
|
479
565
|
if (response.status === 204) {
|
|
480
566
|
return "";
|
|
481
567
|
}
|
|
482
|
-
|
|
568
|
+
try {
|
|
569
|
+
return await response.text();
|
|
570
|
+
} catch (error) {
|
|
571
|
+
if (isAbortError(error)) {
|
|
572
|
+
if (externalSignal?.aborted) throw new AbortError("Request was aborted");
|
|
573
|
+
throw new TimeoutError(`Response from '${url}' timed out after ${timeout}ms`);
|
|
574
|
+
}
|
|
575
|
+
throw new ConnectionError(
|
|
576
|
+
`Connection to '${url}' was lost while reading the response: ${errorMessage(error)}`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
483
579
|
} finally {
|
|
484
580
|
clearTimeout(timeoutId);
|
|
485
581
|
externalSignal?.removeEventListener("abort", onAbort);
|
|
@@ -515,6 +611,10 @@ var HttpClient = class {
|
|
|
515
611
|
}
|
|
516
612
|
}
|
|
517
613
|
};
|
|
614
|
+
function isOutcomeUnknown(error) {
|
|
615
|
+
if (error instanceof TimeoutError || error instanceof InvalidResponseError) return true;
|
|
616
|
+
return error instanceof ConnectionError && !error.notSent;
|
|
617
|
+
}
|
|
518
618
|
function isAbortError(error) {
|
|
519
619
|
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
520
620
|
}
|
|
@@ -611,6 +711,33 @@ var CORE_FEATURE_SET = new Set(CORE_FEATURES);
|
|
|
611
711
|
function isCoreFeature(feature) {
|
|
612
712
|
return CORE_FEATURE_SET.has(feature);
|
|
613
713
|
}
|
|
714
|
+
var FEATURE_CALLS = {
|
|
715
|
+
testConnection: { intent: "read", optsIndex: 0 },
|
|
716
|
+
listDomains: { intent: "read", optsIndex: 0 },
|
|
717
|
+
getDomain: { intent: "read", optsIndex: 1 },
|
|
718
|
+
checkAvailability: { intent: "read", optsIndex: 1 },
|
|
719
|
+
getPricing: { intent: "read", optsIndex: 1 },
|
|
720
|
+
registerDomain: { intent: "write", optsIndex: 2 },
|
|
721
|
+
renewDomain: { intent: "write", optsIndex: 2 },
|
|
722
|
+
setAutoRenew: { intent: "write", optsIndex: 2 },
|
|
723
|
+
transferIn: { intent: "write", optsIndex: 2 },
|
|
724
|
+
updateNameservers: { intent: "write", optsIndex: 2 },
|
|
725
|
+
getNameservers: { intent: "read", optsIndex: 1 },
|
|
726
|
+
lockDomain: { intent: "write", optsIndex: 1 },
|
|
727
|
+
unlockDomain: { intent: "write", optsIndex: 1 },
|
|
728
|
+
setPrivacy: { intent: "write", optsIndex: 2 },
|
|
729
|
+
getContacts: { intent: "read", optsIndex: 1 },
|
|
730
|
+
updateContacts: { intent: "write", optsIndex: 2 },
|
|
731
|
+
getDnsRecords: { intent: "read", optsIndex: 1 },
|
|
732
|
+
setDnsRecords: { intent: "write", optsIndex: 2 },
|
|
733
|
+
getAuthCode: { intent: "write", optsIndex: 1 },
|
|
734
|
+
getDnssec: { intent: "read", optsIndex: 1 },
|
|
735
|
+
disableDnssec: { intent: "write", optsIndex: 1 },
|
|
736
|
+
getEmailForwarding: { intent: "read", optsIndex: 1 },
|
|
737
|
+
setEmailForwarding: { intent: "write", optsIndex: 2 },
|
|
738
|
+
getDomainForwarding: { intent: "read", optsIndex: 1 },
|
|
739
|
+
setDomainForwarding: { intent: "write", optsIndex: 2 }
|
|
740
|
+
};
|
|
614
741
|
|
|
615
742
|
// src/registrar.ts
|
|
616
743
|
function selectBaseUrl(registrar, environment, urls) {
|
|
@@ -636,6 +763,9 @@ var BaseRegistrar = class {
|
|
|
636
763
|
static get features() {
|
|
637
764
|
return [...CORE_FEATURES, ...this.extendedFeatures];
|
|
638
765
|
}
|
|
766
|
+
// HTTP methods this provider's API reserves for reads; see
|
|
767
|
+
// `HttpClientConfig.safeMethods`. REST providers set `REST_SAFE_METHODS`.
|
|
768
|
+
static safeMethods = [];
|
|
639
769
|
// instance mirror of the static `features`
|
|
640
770
|
get features() {
|
|
641
771
|
return this.constructor.features;
|
|
@@ -654,7 +784,39 @@ var BaseRegistrar = class {
|
|
|
654
784
|
this.credentials = credentials;
|
|
655
785
|
this.environment = environment;
|
|
656
786
|
this.options = { ...DEFAULT_OPTIONS, ...clientOptions };
|
|
657
|
-
this.http = new HttpClient({
|
|
787
|
+
this.http = new HttpClient({
|
|
788
|
+
safeMethods: this.constructor.safeMethods,
|
|
789
|
+
...httpConfig,
|
|
790
|
+
options: this.options
|
|
791
|
+
});
|
|
792
|
+
this.threadFeatureCalls();
|
|
793
|
+
}
|
|
794
|
+
// Wrap each feature method so its `RequestOptions` carry a `FeatureCall`:
|
|
795
|
+
// the read/write intent the HTTP layer needs to decide what may be re-sent,
|
|
796
|
+
// and a slot where it reports a write whose outcome is unknown. Providers
|
|
797
|
+
// already forward their options into every request, so nothing else changes.
|
|
798
|
+
//
|
|
799
|
+
// A feature calling another feature (a write re-reading the domain, say)
|
|
800
|
+
// goes through that feature's own wrapper, so the inner read is still a read.
|
|
801
|
+
// Providers fold most failures into an `OperationResult`; the unknown outcome
|
|
802
|
+
// is copied onto that result so it is not lost along with the error.
|
|
803
|
+
threadFeatureCalls() {
|
|
804
|
+
const self = this;
|
|
805
|
+
for (const [feature, { intent, optsIndex }] of Object.entries(FEATURE_CALLS)) {
|
|
806
|
+
const original = self[feature];
|
|
807
|
+
if (typeof original !== "function") continue;
|
|
808
|
+
self[feature] = (...args) => {
|
|
809
|
+
const call = { intent, feature };
|
|
810
|
+
const next = [...args];
|
|
811
|
+
while (next.length <= optsIndex) next.push(void 0);
|
|
812
|
+
next[optsIndex] = { ...next[optsIndex], call };
|
|
813
|
+
const result = original.apply(this, next);
|
|
814
|
+
if (!(result instanceof Promise)) return result;
|
|
815
|
+
return result.then(
|
|
816
|
+
(value) => call.outcomeUnknown && value !== null && typeof value === "object" && value.success === false ? { ...value, outcome: "unknown" } : value
|
|
817
|
+
);
|
|
818
|
+
};
|
|
819
|
+
}
|
|
658
820
|
}
|
|
659
821
|
testConnection(_opts) {
|
|
660
822
|
return Promise.reject(
|
|
@@ -745,6 +907,8 @@ var BaseRegistrar = class {
|
|
|
745
907
|
// src/registrars/cloudflare.ts
|
|
746
908
|
var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
747
909
|
name = "cloudflare";
|
|
910
|
+
// a REST API: GET and HEAD never change state
|
|
911
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
748
912
|
static displayName = "Cloudflare";
|
|
749
913
|
static website = "cloudflare.com";
|
|
750
914
|
static helpText = 'Create an API token in your Cloudflare dashboard under My Profile > API Tokens. The token needs "Account.Registrar" read/write permissions. Find your Account ID in the URL or on the Overview page of any zone.';
|
|
@@ -1026,8 +1190,8 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
|
|
|
1026
1190
|
available: c?.registrable ?? false
|
|
1027
1191
|
};
|
|
1028
1192
|
if (c?.tier) availability.premium = c.tier === "premium";
|
|
1029
|
-
const
|
|
1030
|
-
if (
|
|
1193
|
+
const price2 = toPrice(c?.pricing?.registration_cost);
|
|
1194
|
+
if (price2 != null) availability.price = price2;
|
|
1031
1195
|
if (c?.pricing?.currency) availability.currency = c.pricing.currency;
|
|
1032
1196
|
return availability;
|
|
1033
1197
|
});
|
|
@@ -1602,6 +1766,8 @@ var SEARCH_BATCH_SIZE = 100;
|
|
|
1602
1766
|
var SUPPORTED_DNS_TYPES = /* @__PURE__ */ new Set(["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA"]);
|
|
1603
1767
|
var DynadotRegistrar = class extends BaseRegistrar {
|
|
1604
1768
|
name = "dynadot";
|
|
1769
|
+
// a REST API: GET and HEAD never change state
|
|
1770
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
1605
1771
|
static displayName = "Dynadot";
|
|
1606
1772
|
static website = "dynadot.com";
|
|
1607
1773
|
static helpText = "Under Tools > API in your Dynadot account, generate an API Key and API Secret. The account must be unlocked with API access enabled. The key identifies you; the secret signs requests (X-Signature) and is never sent directly.";
|
|
@@ -1789,14 +1955,14 @@ ${bodyStr}`;
|
|
|
1789
1955
|
opts
|
|
1790
1956
|
);
|
|
1791
1957
|
for (const r of data.domain_result_list ?? []) {
|
|
1792
|
-
const
|
|
1958
|
+
const price2 = oneYearPrice(r.price_list);
|
|
1793
1959
|
results.push({
|
|
1794
1960
|
domainName: r.domain_name ?? "",
|
|
1795
1961
|
available: r.available === "Yes",
|
|
1796
1962
|
premium: r.premium != null ? r.premium.toLowerCase() === "yes" : void 0,
|
|
1797
|
-
price:
|
|
1798
|
-
currency:
|
|
1799
|
-
period:
|
|
1963
|
+
price: price2?.registration,
|
|
1964
|
+
currency: price2?.currency,
|
|
1965
|
+
period: price2 ? 1 : void 0
|
|
1800
1966
|
});
|
|
1801
1967
|
}
|
|
1802
1968
|
}
|
|
@@ -1833,13 +1999,13 @@ ${bodyStr}`;
|
|
|
1833
1999
|
opts
|
|
1834
2000
|
);
|
|
1835
2001
|
const result = (data.domain_result_list ?? [])[0];
|
|
1836
|
-
const
|
|
2002
|
+
const price2 = oneYearPrice(result?.price_list);
|
|
1837
2003
|
return {
|
|
1838
2004
|
tld,
|
|
1839
|
-
currency:
|
|
1840
|
-
registration:
|
|
1841
|
-
renewal:
|
|
1842
|
-
transfer:
|
|
2005
|
+
currency: price2?.currency ?? "USD",
|
|
2006
|
+
registration: price2?.registration,
|
|
2007
|
+
renewal: price2?.renewal,
|
|
2008
|
+
transfer: price2?.transfer
|
|
1843
2009
|
};
|
|
1844
2010
|
}
|
|
1845
2011
|
/**
|
|
@@ -2353,6 +2519,8 @@ var GANDI_TYPE_TO_FORWARD = {
|
|
|
2353
2519
|
};
|
|
2354
2520
|
var GandiRegistrar = class extends BaseRegistrar {
|
|
2355
2521
|
name = "gandi";
|
|
2522
|
+
// a REST API: GET and HEAD never change state
|
|
2523
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
2356
2524
|
static displayName = "Gandi.net";
|
|
2357
2525
|
static website = "gandi.net";
|
|
2358
2526
|
static helpText = "Generate a Personal Access Token (PAT) in your Gandi account under Account Settings > Security. The token needs permission to manage domains. (Gandi has deprecated the legacy API Key; use a PAT.)";
|
|
@@ -2555,17 +2723,17 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
2555
2723
|
const products = resp.products ?? [];
|
|
2556
2724
|
const product = products.find((p) => p.process === "create") ?? products[0];
|
|
2557
2725
|
const status = product?.status ?? "";
|
|
2558
|
-
const
|
|
2726
|
+
const price2 = product?.prices?.[0];
|
|
2559
2727
|
const result = {
|
|
2560
2728
|
domainName: product?.name ?? domainName,
|
|
2561
2729
|
available: status.startsWith("available"),
|
|
2562
|
-
premium: status.includes("premium") ||
|
|
2730
|
+
premium: status.includes("premium") || price2?.type === "premium"
|
|
2563
2731
|
};
|
|
2564
|
-
const amount = pickPrice(
|
|
2732
|
+
const amount = pickPrice(price2);
|
|
2565
2733
|
if (amount != null) result.price = amount;
|
|
2566
2734
|
if (resp.currency) result.currency = resp.currency;
|
|
2567
|
-
if (
|
|
2568
|
-
result.period =
|
|
2735
|
+
if (price2?.duration_unit === "y" && price2.min_duration != null) {
|
|
2736
|
+
result.period = price2.min_duration;
|
|
2569
2737
|
}
|
|
2570
2738
|
results.push(result);
|
|
2571
2739
|
}
|
|
@@ -3003,9 +3171,9 @@ function fromGandiContact(c) {
|
|
|
3003
3171
|
country: c.country ?? ""
|
|
3004
3172
|
};
|
|
3005
3173
|
}
|
|
3006
|
-
function pickPrice(
|
|
3007
|
-
if (!
|
|
3008
|
-
const value =
|
|
3174
|
+
function pickPrice(price2) {
|
|
3175
|
+
if (!price2) return void 0;
|
|
3176
|
+
const value = price2.price_after_taxes ?? price2.price_before_taxes;
|
|
3009
3177
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3010
3178
|
}
|
|
3011
3179
|
function parseDsRecord(ds) {
|
|
@@ -3040,6 +3208,8 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
3040
3208
|
var PRICE_MICRO_UNITS = 1e6;
|
|
3041
3209
|
var GoDaddyRegistrar = class _GoDaddyRegistrar extends BaseRegistrar {
|
|
3042
3210
|
name = "godaddy";
|
|
3211
|
+
// a REST API: GET and HEAD never change state
|
|
3212
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
3043
3213
|
static displayName = "GoDaddy";
|
|
3044
3214
|
static website = "godaddy.com";
|
|
3045
3215
|
static helpText = "Create a Personal Access Token (PAT) at https://developer.godaddy.com and paste it here. The PAT is GoDaddy's modern credential: it unlocks the v3 API (discovery, registration, DNS, nameservers) and also authenticates the legacy v1 API for management operations (renew, transfer, auto-renew, lock, privacy, contacts) \u2014 one token covers everything.";
|
|
@@ -3920,6 +4090,8 @@ function dnsFieldsDiffer(a, b) {
|
|
|
3920
4090
|
var TOKEN_URL = "https://api.namebright.com/auth/token";
|
|
3921
4091
|
var NameBrightRegistrar = class extends BaseRegistrar {
|
|
3922
4092
|
name = "namebright";
|
|
4093
|
+
// a REST API: GET and HEAD never change state
|
|
4094
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
3923
4095
|
static displayName = "NameBright";
|
|
3924
4096
|
static website = "namebright.com";
|
|
3925
4097
|
static helpText = 'API access is not enabled by default \u2014 you must request it from NameBright (contact support / your account manager) before the API Management page is available. Once enabled, create an API Application under my.namebright.com > My Account > API Management. The Client ID is "<accountName>:<applicationName>"; NameBright issues the Client Secret. Note the application also enforces an IP whitelist.';
|
|
@@ -3958,7 +4130,8 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
3958
4130
|
client_id: this.credentials.clientId,
|
|
3959
4131
|
client_secret: this.credentials.clientSecret
|
|
3960
4132
|
},
|
|
3961
|
-
|
|
4133
|
+
// issuing a token changes nothing on the account, whatever it precedes
|
|
4134
|
+
...asRead(opts)
|
|
3962
4135
|
});
|
|
3963
4136
|
if (!res.access_token) {
|
|
3964
4137
|
throw new AuthenticationError("NameBright: token endpoint returned no access_token");
|
|
@@ -4089,13 +4262,13 @@ var NameBrightRegistrar = class extends BaseRegistrar {
|
|
|
4089
4262
|
{ path: `purchase/availability/${encodeURIComponent(domainName)}` },
|
|
4090
4263
|
opts
|
|
4091
4264
|
);
|
|
4092
|
-
const
|
|
4265
|
+
const price2 = res.Promotion?.PromotionPrice ?? res.UnitPrice;
|
|
4093
4266
|
const result = {
|
|
4094
4267
|
domainName: res.DomainName ?? domainName,
|
|
4095
4268
|
available: res.Status === "AvailableForRegistration"
|
|
4096
4269
|
};
|
|
4097
|
-
if (typeof
|
|
4098
|
-
result.price =
|
|
4270
|
+
if (typeof price2 === "number" && Number.isFinite(price2) && price2 > 0) {
|
|
4271
|
+
result.price = price2;
|
|
4099
4272
|
result.currency = "USD";
|
|
4100
4273
|
}
|
|
4101
4274
|
results.push(result);
|
|
@@ -4737,7 +4910,11 @@ var NamecheapRegistrar = class extends BaseRegistrar {
|
|
|
4737
4910
|
* has no WhoisGuard allotted (e.g. TLDs that don't offer it).
|
|
4738
4911
|
*/
|
|
4739
4912
|
async setPrivacy(domainName, enabled, opts) {
|
|
4740
|
-
const cr = await this.command(
|
|
4913
|
+
const cr = await this.command(
|
|
4914
|
+
"namecheap.domains.getInfo",
|
|
4915
|
+
{ DomainName: domainName },
|
|
4916
|
+
asRead(opts)
|
|
4917
|
+
);
|
|
4741
4918
|
const id = cr.DomainGetInfoResult?.Whoisguard?.ID;
|
|
4742
4919
|
if (id == null || String(id) === "0") {
|
|
4743
4920
|
throw new Error(`${this.name}: ${domainName} has no WhoisGuard subscription to toggle`);
|
|
@@ -4928,9 +5105,9 @@ function splitDomain(domain) {
|
|
|
4928
5105
|
const dot = domain.indexOf(".");
|
|
4929
5106
|
return dot === -1 ? { sld: domain, tld: "" } : { sld: domain.slice(0, dot), tld: domain.slice(dot + 1) };
|
|
4930
5107
|
}
|
|
4931
|
-
function toPrice2(
|
|
4932
|
-
if (!
|
|
4933
|
-
const n = Number(
|
|
5108
|
+
function toPrice2(price2) {
|
|
5109
|
+
if (!price2?.["@_Price"]) return void 0;
|
|
5110
|
+
const n = Number(price2["@_Price"]);
|
|
4934
5111
|
return Number.isFinite(n) ? n : void 0;
|
|
4935
5112
|
}
|
|
4936
5113
|
function fromNcContact(c) {
|
|
@@ -4969,6 +5146,505 @@ function toNcContactParams(prefix, c) {
|
|
|
4969
5146
|
return params;
|
|
4970
5147
|
}
|
|
4971
5148
|
|
|
5149
|
+
// src/registrars/namecom.ts
|
|
5150
|
+
var NamecomHttpClient = class extends HttpClient {
|
|
5151
|
+
async request(req) {
|
|
5152
|
+
try {
|
|
5153
|
+
return await super.request(req);
|
|
5154
|
+
} catch (error) {
|
|
5155
|
+
if (error instanceof ParsingError) throw new ParsingError("namecom: invalid JSON response");
|
|
5156
|
+
if (error instanceof ConnectionError)
|
|
5157
|
+
throw new ConnectionError("namecom: network request failed", { notSent: error.notSent });
|
|
5158
|
+
throw error;
|
|
5159
|
+
}
|
|
5160
|
+
}
|
|
5161
|
+
async toStatusError(response, url) {
|
|
5162
|
+
await response.body?.cancel().catch(() => void 0);
|
|
5163
|
+
const error = await super.toStatusError(
|
|
5164
|
+
new Response(null, {
|
|
5165
|
+
status: response.status,
|
|
5166
|
+
headers: response.headers
|
|
5167
|
+
}),
|
|
5168
|
+
url
|
|
5169
|
+
);
|
|
5170
|
+
if (error instanceof RateLimitError) {
|
|
5171
|
+
const retryAfter = response.headers.get("retry-after");
|
|
5172
|
+
const seconds = retryAfter == null ? NaN : Number(retryAfter);
|
|
5173
|
+
const date = retryAfter == null ? NaN : Date.parse(retryAfter);
|
|
5174
|
+
const reset = response.headers.get("x-ratelimit-reset");
|
|
5175
|
+
error.retryAfter = Number.isFinite(seconds) ? Math.max(0, seconds) : Number.isFinite(date) ? Math.max(0, (date - Date.now()) / 1e3) : reset != null && Number.isFinite(Number(reset)) ? Math.max(0, Number(reset) - Date.now() / 1e3) : void 0;
|
|
5176
|
+
}
|
|
5177
|
+
return error;
|
|
5178
|
+
}
|
|
5179
|
+
};
|
|
5180
|
+
var NamecomRegistrar = class extends BaseRegistrar {
|
|
5181
|
+
name = "namecom";
|
|
5182
|
+
// a REST API: GET and HEAD never change state
|
|
5183
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
5184
|
+
static displayName = "Name.com";
|
|
5185
|
+
static website = "name.com";
|
|
5186
|
+
static supportsSandbox = true;
|
|
5187
|
+
static configFields = [
|
|
5188
|
+
{ name: "username", label: "Username", type: "text", required: true },
|
|
5189
|
+
{ name: "apiToken", label: "API token", type: "password", required: true }
|
|
5190
|
+
];
|
|
5191
|
+
static helpText = "Get an API token at https://www.name.com/account/settings/api. Accounts with 2FA must enable API Access. Sandbox requires your -test username and a separate sandbox token; select the sandbox environment explicitly.";
|
|
5192
|
+
static extendedFeatures = [Feature.GetAuthCode];
|
|
5193
|
+
requiresNameserversFetch = false;
|
|
5194
|
+
constructor(credentials, options) {
|
|
5195
|
+
const username = credentials.username ?? "";
|
|
5196
|
+
if (username.includes(":"))
|
|
5197
|
+
throw new ConfigurationError("namecom: username cannot contain a colon");
|
|
5198
|
+
const auth = btoa(
|
|
5199
|
+
Array.from(
|
|
5200
|
+
new TextEncoder().encode(`${username}:${credentials.apiToken ?? ""}`),
|
|
5201
|
+
(byte) => String.fromCharCode(byte)
|
|
5202
|
+
).join("")
|
|
5203
|
+
);
|
|
5204
|
+
const config = {
|
|
5205
|
+
baseUrl: selectBaseUrl("namecom", options?.environment, {
|
|
5206
|
+
production: "https://api.name.com/core/v1",
|
|
5207
|
+
sandbox: "https://api.dev.name.com/core/v1"
|
|
5208
|
+
}),
|
|
5209
|
+
headers: { Authorization: `Basic ${auth}` }
|
|
5210
|
+
};
|
|
5211
|
+
super(credentials, config, options);
|
|
5212
|
+
this.http = new NamecomHttpClient({
|
|
5213
|
+
...config,
|
|
5214
|
+
safeMethods: REST_SAFE_METHODS,
|
|
5215
|
+
options: this.options
|
|
5216
|
+
});
|
|
5217
|
+
}
|
|
5218
|
+
async testConnection(opts) {
|
|
5219
|
+
try {
|
|
5220
|
+
await this.http.request({ path: "/domains", query: { perPage: 1, page: 1 }, ...opts });
|
|
5221
|
+
return { success: true, message: "Connection successful" };
|
|
5222
|
+
} catch (error) {
|
|
5223
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
async listDomains(opts) {
|
|
5227
|
+
const { search, detailed: _detailed, ...requestOpts } = opts ?? {};
|
|
5228
|
+
const domains = await this.pages("/domains", "domains", requestOpts);
|
|
5229
|
+
return filterDomains(
|
|
5230
|
+
domains.map((d) => this.toDomain(d)),
|
|
5231
|
+
search
|
|
5232
|
+
);
|
|
5233
|
+
}
|
|
5234
|
+
async getDomain(domainName, opts) {
|
|
5235
|
+
return this.toDomain(await this.rawDomain(domainName, opts));
|
|
5236
|
+
}
|
|
5237
|
+
async getNameservers(domainName, opts) {
|
|
5238
|
+
return (await this.getDomain(domainName, opts)).nameservers;
|
|
5239
|
+
}
|
|
5240
|
+
async getAuthCode(domainName, opts) {
|
|
5241
|
+
const result = await this.http.request({
|
|
5242
|
+
path: `${domainPath(domainName)}:getAuthCode`,
|
|
5243
|
+
...opts
|
|
5244
|
+
});
|
|
5245
|
+
if (!result?.authCode) throw new InvalidResponseError("namecom: missing authorization code");
|
|
5246
|
+
return result.authCode;
|
|
5247
|
+
}
|
|
5248
|
+
async checkAvailability(domainNames, opts) {
|
|
5249
|
+
const results = [];
|
|
5250
|
+
for (let i = 0; i < domainNames.length; i += 50) {
|
|
5251
|
+
const res = await this.http.request({
|
|
5252
|
+
method: "POST",
|
|
5253
|
+
path: "/domains:checkAvailability",
|
|
5254
|
+
body: {
|
|
5255
|
+
domainNames: domainNames.slice(i, i + 50).map(normalizeDomain),
|
|
5256
|
+
purchaseType: "registration"
|
|
5257
|
+
},
|
|
5258
|
+
...opts
|
|
5259
|
+
});
|
|
5260
|
+
if (!Array.isArray(res?.results))
|
|
5261
|
+
throw new InvalidResponseError("namecom: missing availability results");
|
|
5262
|
+
results.push(
|
|
5263
|
+
...res.results.map((r) => ({
|
|
5264
|
+
domainName: r.domainName,
|
|
5265
|
+
available: (r.purchasable ?? false) && (!r.purchaseType || r.purchaseType === "registration"),
|
|
5266
|
+
premium: r.premium ?? false,
|
|
5267
|
+
price: price(r.purchasePrice),
|
|
5268
|
+
currency: "USD"
|
|
5269
|
+
// Discovery quotes the TLD's minimum term, which isn't always one year.
|
|
5270
|
+
}))
|
|
5271
|
+
);
|
|
5272
|
+
}
|
|
5273
|
+
return results;
|
|
5274
|
+
}
|
|
5275
|
+
async getPricing(tldOrDomain, opts) {
|
|
5276
|
+
const name = normalizeDomain(tldOrDomain);
|
|
5277
|
+
if (name.startsWith(".") || !name.includes(".")) {
|
|
5278
|
+
const tld = name.replace(/^\./, "");
|
|
5279
|
+
const entries = await this.pages("/tldpricing", "pricing", opts, {
|
|
5280
|
+
tlds: tld,
|
|
5281
|
+
duration: 1
|
|
5282
|
+
});
|
|
5283
|
+
const entry = entries.find((p) => p.tld.toLowerCase() === tld && p.duration === 1);
|
|
5284
|
+
if (!entry) throw new NotFoundError(`namecom: no one-year pricing for .${tld}`);
|
|
5285
|
+
return {
|
|
5286
|
+
tld,
|
|
5287
|
+
currency: "USD",
|
|
5288
|
+
registration: price(entry.registrationPrice),
|
|
5289
|
+
renewal: price(entry.renewalPrice),
|
|
5290
|
+
transfer: price(entry.transferInPrice)
|
|
5291
|
+
};
|
|
5292
|
+
}
|
|
5293
|
+
const result = await this.domainPricing(name, 1, opts);
|
|
5294
|
+
return {
|
|
5295
|
+
tld: name.slice(name.indexOf(".") + 1),
|
|
5296
|
+
currency: "USD",
|
|
5297
|
+
registration: price(result.purchasePrice),
|
|
5298
|
+
renewal: price(result.renewalPrice),
|
|
5299
|
+
transfer: price(result.transferPrice)
|
|
5300
|
+
};
|
|
5301
|
+
}
|
|
5302
|
+
async registerDomain(domainName, input, opts) {
|
|
5303
|
+
const years = validYears(input.years ?? 1);
|
|
5304
|
+
if (!input.contacts.registrant)
|
|
5305
|
+
throw new ConfigurationError("namecom: a registrant contact is required");
|
|
5306
|
+
const contacts = toContacts(input.contacts);
|
|
5307
|
+
const availability = (await this.checkAvailability([domainName], opts)).find(
|
|
5308
|
+
(result) => normalizeDomain(result.domainName) === normalizeDomain(domainName)
|
|
5309
|
+
);
|
|
5310
|
+
if (!availability?.available)
|
|
5311
|
+
return { success: false, message: "namecom: domain is not available for registration" };
|
|
5312
|
+
const pricing = await this.domainPricing(domainName, years, opts);
|
|
5313
|
+
const purchasePrice = purchaseQuote(pricing, "purchasePrice", availability.premium);
|
|
5314
|
+
return this.purchase(
|
|
5315
|
+
"/domains",
|
|
5316
|
+
{
|
|
5317
|
+
domain: {
|
|
5318
|
+
domainName: normalizeDomain(domainName),
|
|
5319
|
+
contacts,
|
|
5320
|
+
nameservers: input.nameservers,
|
|
5321
|
+
privacyEnabled: input.privacy,
|
|
5322
|
+
autorenewEnabled: input.autoRenew
|
|
5323
|
+
},
|
|
5324
|
+
purchaseType: "registration",
|
|
5325
|
+
years,
|
|
5326
|
+
purchasePrice
|
|
5327
|
+
},
|
|
5328
|
+
domainName,
|
|
5329
|
+
"registration",
|
|
5330
|
+
opts
|
|
5331
|
+
);
|
|
5332
|
+
}
|
|
5333
|
+
async renewDomain(domainName, years = 1, opts) {
|
|
5334
|
+
validYears(years);
|
|
5335
|
+
const pricing = await this.domainPricing(domainName, years, opts);
|
|
5336
|
+
return this.purchase(
|
|
5337
|
+
`${domainPath(domainName)}:renew`,
|
|
5338
|
+
{
|
|
5339
|
+
years,
|
|
5340
|
+
purchasePrice: purchaseQuote(pricing, "renewalPrice")
|
|
5341
|
+
},
|
|
5342
|
+
domainName,
|
|
5343
|
+
"renewal",
|
|
5344
|
+
opts
|
|
5345
|
+
);
|
|
5346
|
+
}
|
|
5347
|
+
async transferIn(domainName, input, opts) {
|
|
5348
|
+
if (!input.authCode) throw new ConfigurationError("namecom: an authorization code is required");
|
|
5349
|
+
if (input.years != null || input.contacts != null || input.autoRenew != null) {
|
|
5350
|
+
throw new ConfigurationError(
|
|
5351
|
+
"namecom: transferIn does not accept years, contacts or autoRenew; the registry determines the term"
|
|
5352
|
+
);
|
|
5353
|
+
}
|
|
5354
|
+
const pricing = await this.domainPricing(domainName, void 0, opts);
|
|
5355
|
+
return this.purchase(
|
|
5356
|
+
"/transfers",
|
|
5357
|
+
{
|
|
5358
|
+
domainName: normalizeDomain(domainName),
|
|
5359
|
+
authCode: input.authCode,
|
|
5360
|
+
privacyEnabled: input.privacy,
|
|
5361
|
+
purchasePrice: purchaseQuote(pricing, "transferPrice")
|
|
5362
|
+
},
|
|
5363
|
+
domainName,
|
|
5364
|
+
"transfer",
|
|
5365
|
+
opts
|
|
5366
|
+
);
|
|
5367
|
+
}
|
|
5368
|
+
setAutoRenew(domainName, enabled, opts) {
|
|
5369
|
+
return this.patchDomain(domainName, { autorenewEnabled: enabled }, opts);
|
|
5370
|
+
}
|
|
5371
|
+
lockDomain(domainName, opts) {
|
|
5372
|
+
return this.patchDomain(domainName, { locked: true }, opts);
|
|
5373
|
+
}
|
|
5374
|
+
unlockDomain(domainName, opts) {
|
|
5375
|
+
return this.patchDomain(domainName, { locked: false }, opts);
|
|
5376
|
+
}
|
|
5377
|
+
setPrivacy(domainName, enabled, opts) {
|
|
5378
|
+
return this.patchDomain(domainName, { privacyEnabled: enabled }, opts);
|
|
5379
|
+
}
|
|
5380
|
+
updateNameservers(domainName, nameservers, opts) {
|
|
5381
|
+
return this.mutate(
|
|
5382
|
+
{ method: "POST", path: `${domainPath(domainName)}:setNameservers`, body: { nameservers } },
|
|
5383
|
+
opts
|
|
5384
|
+
);
|
|
5385
|
+
}
|
|
5386
|
+
async getContacts(domainName, opts) {
|
|
5387
|
+
const result = {};
|
|
5388
|
+
const raw = await this.rawDomain(domainName, opts);
|
|
5389
|
+
for (const role of CONTACT_ROLES) {
|
|
5390
|
+
const c = raw.contacts?.[role];
|
|
5391
|
+
if (c)
|
|
5392
|
+
result[role] = {
|
|
5393
|
+
firstName: c.firstName ?? "",
|
|
5394
|
+
lastName: c.lastName ?? "",
|
|
5395
|
+
organization: c.companyName ?? void 0,
|
|
5396
|
+
email: c.email ?? "",
|
|
5397
|
+
phone: c.phone ?? "",
|
|
5398
|
+
fax: c.fax ?? void 0,
|
|
5399
|
+
address1: c.address1 ?? "",
|
|
5400
|
+
address2: c.address2 ?? void 0,
|
|
5401
|
+
city: c.city ?? "",
|
|
5402
|
+
state: c.state ?? void 0,
|
|
5403
|
+
postalCode: c.zip ?? "",
|
|
5404
|
+
country: c.country ?? ""
|
|
5405
|
+
};
|
|
5406
|
+
}
|
|
5407
|
+
return result;
|
|
5408
|
+
}
|
|
5409
|
+
updateContacts(domainName, contacts, opts) {
|
|
5410
|
+
return this.mutate(
|
|
5411
|
+
{
|
|
5412
|
+
method: "POST",
|
|
5413
|
+
path: `${domainPath(domainName)}:setContacts`,
|
|
5414
|
+
body: { contacts: toContacts(contacts) }
|
|
5415
|
+
},
|
|
5416
|
+
opts
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5419
|
+
async getDnsRecords(domainName, opts) {
|
|
5420
|
+
return (await this.pages(`${domainPath(domainName)}/records`, "records", opts)).map(fromRecord);
|
|
5421
|
+
}
|
|
5422
|
+
// No bulk replace endpoint. Reconcile by id: preserve identical records, update
|
|
5423
|
+
// existing type/host pairs, create additions, then delete stale records. This
|
|
5424
|
+
// avoids duplicate-record errors and does not empty the zone before creation.
|
|
5425
|
+
async setDnsRecords(domainName, records, opts) {
|
|
5426
|
+
const desired = records.map(toRecord).filter((record, index, all) => all.findIndex((other) => sameRecord(other, record)) === index);
|
|
5427
|
+
try {
|
|
5428
|
+
const path = `${domainPath(domainName)}/records`;
|
|
5429
|
+
const existing = await this.pages(path, "records", opts);
|
|
5430
|
+
if (existing.some((r) => !Number.isInteger(r.id) || r.id <= 0))
|
|
5431
|
+
throw new InvalidResponseError("namecom: DNS response missing record ids");
|
|
5432
|
+
const remaining = [...existing];
|
|
5433
|
+
const pending = [];
|
|
5434
|
+
for (const record of desired) {
|
|
5435
|
+
const index = remaining.findIndex((r) => sameRecord(r, record));
|
|
5436
|
+
if (index >= 0) remaining.splice(index, 1);
|
|
5437
|
+
else if (!pending.some((r) => sameRecord(r, record))) pending.push(record);
|
|
5438
|
+
}
|
|
5439
|
+
for (const record of pending) {
|
|
5440
|
+
const index = remaining.findIndex((r) => recordKey(r) === recordKey(record));
|
|
5441
|
+
const old = index >= 0 ? remaining.splice(index, 1)[0] : void 0;
|
|
5442
|
+
await this.http.request({
|
|
5443
|
+
...opts,
|
|
5444
|
+
method: old ? "PUT" : "POST",
|
|
5445
|
+
path: old ? `${path}/${old.id}` : path,
|
|
5446
|
+
body: record
|
|
5447
|
+
});
|
|
5448
|
+
}
|
|
5449
|
+
for (const old of remaining) {
|
|
5450
|
+
await this.http.request({
|
|
5451
|
+
...opts,
|
|
5452
|
+
method: "DELETE",
|
|
5453
|
+
path: `${path}/${old.id}`
|
|
5454
|
+
});
|
|
5455
|
+
}
|
|
5456
|
+
return { success: true, message: "DNS records updated successfully" };
|
|
5457
|
+
} catch (error) {
|
|
5458
|
+
return {
|
|
5459
|
+
success: false,
|
|
5460
|
+
message: `${toRegistrarError(error).message}. DNS replacement is not atomic; read the zone before retrying.`
|
|
5461
|
+
};
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5464
|
+
patchDomain(domainName, body, opts) {
|
|
5465
|
+
return this.mutate({ method: "PATCH", path: domainPath(domainName), body }, opts);
|
|
5466
|
+
}
|
|
5467
|
+
rawDomain(domainName, opts) {
|
|
5468
|
+
return this.http.request({ path: domainPath(domainName), ...opts });
|
|
5469
|
+
}
|
|
5470
|
+
domainPricing(domainName, years, opts) {
|
|
5471
|
+
return this.http.request({
|
|
5472
|
+
path: `${domainPath(domainName)}:getPricing`,
|
|
5473
|
+
query: { years },
|
|
5474
|
+
...opts
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5477
|
+
toDomain(d) {
|
|
5478
|
+
if (!d?.domainName)
|
|
5479
|
+
throw new InvalidResponseError("namecom: domain response missing domainName");
|
|
5480
|
+
return createDomain({
|
|
5481
|
+
domainName: normalizeDomain(d.domainName),
|
|
5482
|
+
registrar: this.name,
|
|
5483
|
+
// Core exposes locks, not a lifecycle status or a scheduled renewal date.
|
|
5484
|
+
status: (d.locks ?? []).join(","),
|
|
5485
|
+
createdDate: d.createDate,
|
|
5486
|
+
expirationDate: d.expireDate,
|
|
5487
|
+
autoRenew: d.autorenewEnabled ?? false,
|
|
5488
|
+
locked: d.locked ?? false,
|
|
5489
|
+
privacy: d.privacyEnabled ?? false,
|
|
5490
|
+
nameservers: d.nameservers
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
async pages(path, key, opts, query) {
|
|
5494
|
+
const result = [];
|
|
5495
|
+
let page = 1;
|
|
5496
|
+
while (true) {
|
|
5497
|
+
const res = await this.http.request({
|
|
5498
|
+
path,
|
|
5499
|
+
query: { perPage: 1e3, page, ...query },
|
|
5500
|
+
...opts
|
|
5501
|
+
});
|
|
5502
|
+
const items = res?.[key];
|
|
5503
|
+
if (!res || items != null && !Array.isArray(items))
|
|
5504
|
+
throw new InvalidResponseError("namecom: invalid list response");
|
|
5505
|
+
result.push(...items ?? []);
|
|
5506
|
+
const next = res.nextPage;
|
|
5507
|
+
if (next == null || next === 0) return result;
|
|
5508
|
+
if (!Number.isInteger(next) || next <= page)
|
|
5509
|
+
throw new InvalidResponseError("namecom: invalid pagination cursor");
|
|
5510
|
+
page = next;
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
async mutate(req, opts) {
|
|
5514
|
+
try {
|
|
5515
|
+
await this.http.request({ ...req, ...opts });
|
|
5516
|
+
return { success: true, message: "Domain updated successfully" };
|
|
5517
|
+
} catch (error) {
|
|
5518
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
async purchase(path, body, domainName, operation, opts) {
|
|
5522
|
+
try {
|
|
5523
|
+
const res = await this.http.request({
|
|
5524
|
+
method: "POST",
|
|
5525
|
+
path,
|
|
5526
|
+
body,
|
|
5527
|
+
...opts,
|
|
5528
|
+
retries: 0
|
|
5529
|
+
});
|
|
5530
|
+
const returnedName = res?.domain?.domainName ?? res?.transfer?.domainName;
|
|
5531
|
+
if (!Number.isInteger(res?.order) || (res.order ?? 0) <= 0 || !returnedName || normalizeDomain(returnedName) !== normalizeDomain(domainName)) {
|
|
5532
|
+
throw new InvalidResponseError(
|
|
5533
|
+
"namecom: purchase response missing order or matching domain"
|
|
5534
|
+
);
|
|
5535
|
+
}
|
|
5536
|
+
return {
|
|
5537
|
+
success: true,
|
|
5538
|
+
message: `Domain ${operation} request accepted (order ${res.order}); verify final domain/transfer status`
|
|
5539
|
+
};
|
|
5540
|
+
} catch (error) {
|
|
5541
|
+
const e = toRegistrarError(error);
|
|
5542
|
+
const uncertain = e.status < 0 || e.status === 408 || e.status >= 500 || e.name === "AbortError" || e instanceof ParsingError;
|
|
5543
|
+
return {
|
|
5544
|
+
success: false,
|
|
5545
|
+
message: e.message + (uncertain ? ". Outcome unknown; check Name.com orders and domain/transfer status before retrying." : "")
|
|
5546
|
+
};
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
};
|
|
5550
|
+
var CONTACT_ROLES = ["registrant", "admin", "tech", "billing"];
|
|
5551
|
+
function toContacts(contacts) {
|
|
5552
|
+
const result = {};
|
|
5553
|
+
for (const role of CONTACT_ROLES) {
|
|
5554
|
+
const c = contacts[role];
|
|
5555
|
+
if (!c) continue;
|
|
5556
|
+
const required = [
|
|
5557
|
+
"firstName",
|
|
5558
|
+
"lastName",
|
|
5559
|
+
"email",
|
|
5560
|
+
"phone",
|
|
5561
|
+
"address1",
|
|
5562
|
+
"city",
|
|
5563
|
+
"state",
|
|
5564
|
+
"postalCode",
|
|
5565
|
+
"country"
|
|
5566
|
+
];
|
|
5567
|
+
if (required.some((k) => !c[k]?.trim()))
|
|
5568
|
+
throw new ConfigurationError(`namecom: ${role} must be a complete contact, including state`);
|
|
5569
|
+
result[role] = {
|
|
5570
|
+
firstName: c.firstName,
|
|
5571
|
+
lastName: c.lastName,
|
|
5572
|
+
companyName: c.organization,
|
|
5573
|
+
email: c.email,
|
|
5574
|
+
phone: c.phone.replace(".", ""),
|
|
5575
|
+
fax: c.fax?.replace(".", ""),
|
|
5576
|
+
address1: c.address1,
|
|
5577
|
+
address2: c.address2,
|
|
5578
|
+
city: c.city,
|
|
5579
|
+
state: c.state,
|
|
5580
|
+
zip: c.postalCode,
|
|
5581
|
+
country: c.country.toUpperCase()
|
|
5582
|
+
};
|
|
5583
|
+
}
|
|
5584
|
+
if (Object.keys(result).length === 0)
|
|
5585
|
+
throw new ConfigurationError("namecom: at least one contact is required");
|
|
5586
|
+
return result;
|
|
5587
|
+
}
|
|
5588
|
+
function domainPath(domainName) {
|
|
5589
|
+
return `/domains/${encodeURIComponent(normalizeDomain(domainName))}`;
|
|
5590
|
+
}
|
|
5591
|
+
function validYears(years) {
|
|
5592
|
+
if (!Number.isInteger(years) || years < 1 || years > 10)
|
|
5593
|
+
throw new ConfigurationError("namecom: years must be an integer from 1 to 10");
|
|
5594
|
+
return years;
|
|
5595
|
+
}
|
|
5596
|
+
function price(value) {
|
|
5597
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
5598
|
+
}
|
|
5599
|
+
function purchaseQuote(pricing, field, premium = false) {
|
|
5600
|
+
const value = price(pricing?.[field]);
|
|
5601
|
+
if (value == null)
|
|
5602
|
+
throw new RegistrarError(
|
|
5603
|
+
`namecom: ${field} unavailable for the requested term; no purchase submitted`
|
|
5604
|
+
);
|
|
5605
|
+
return premium || pricing.premium ? value : void 0;
|
|
5606
|
+
}
|
|
5607
|
+
var DNS_TYPES = /* @__PURE__ */ new Set(["A", "AAAA", "ANAME", "CNAME", "MX", "NS", "SRV", "TXT"]);
|
|
5608
|
+
function toRecord(r) {
|
|
5609
|
+
const type = r.type.toUpperCase();
|
|
5610
|
+
if (!DNS_TYPES.has(type))
|
|
5611
|
+
throw new ConfigurationError(`namecom: DNS type ${type} is not supported`);
|
|
5612
|
+
const ttl = r.ttl ?? 300;
|
|
5613
|
+
if (!Number.isInteger(ttl) || ttl < 300)
|
|
5614
|
+
throw new ConfigurationError("namecom: DNS TTL must be an integer of at least 300 seconds");
|
|
5615
|
+
for (const value of [r.priority, r.weight, r.port]) {
|
|
5616
|
+
if (value != null && (!Number.isInteger(value) || value < 0 || value > 65535))
|
|
5617
|
+
throw new ConfigurationError("namecom: invalid DNS priority, weight or port");
|
|
5618
|
+
}
|
|
5619
|
+
if (!r.value.trim()) throw new ConfigurationError("namecom: DNS answer is required");
|
|
5620
|
+
return {
|
|
5621
|
+
type,
|
|
5622
|
+
host: !r.name || r.name === "@" ? "" : r.name,
|
|
5623
|
+
answer: type === "SRV" ? `${r.weight ?? 0} ${r.port ?? 0} ${r.value}` : r.value,
|
|
5624
|
+
ttl,
|
|
5625
|
+
...["MX", "SRV"].includes(type) ? { priority: r.priority ?? 0 } : {}
|
|
5626
|
+
};
|
|
5627
|
+
}
|
|
5628
|
+
function fromRecord(r) {
|
|
5629
|
+
const type = r.type.toUpperCase();
|
|
5630
|
+
const record = { type, name: r.host || "@", value: r.answer, ttl: r.ttl };
|
|
5631
|
+
if (type === "MX" || type === "SRV") record.priority = r.priority ?? 0;
|
|
5632
|
+
if (type === "SRV") {
|
|
5633
|
+
const match = /^(\d+)\s+(\d+)\s+(.+)$/.exec(r.answer);
|
|
5634
|
+
if (!match) throw new InvalidResponseError("namecom: invalid SRV answer");
|
|
5635
|
+
record.weight = Number(match[1]);
|
|
5636
|
+
record.port = Number(match[2]);
|
|
5637
|
+
record.value = match[3];
|
|
5638
|
+
}
|
|
5639
|
+
return record;
|
|
5640
|
+
}
|
|
5641
|
+
function recordKey(r) {
|
|
5642
|
+
return `${r.type.toUpperCase()}\0${(r.host === "@" ? "" : r.host ?? "").toLowerCase()}`;
|
|
5643
|
+
}
|
|
5644
|
+
function sameRecord(a, b) {
|
|
5645
|
+
return recordKey(a) === recordKey(b) && a.answer === b.answer && (a.ttl ?? 300) === (b.ttl ?? 300) && (a.priority ?? 0) === (b.priority ?? 0);
|
|
5646
|
+
}
|
|
5647
|
+
|
|
4972
5648
|
// src/registrars/namesilo.ts
|
|
4973
5649
|
var NameSiloRegistrar = class extends BaseRegistrar {
|
|
4974
5650
|
name = "namesilo";
|
|
@@ -5159,13 +5835,13 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5159
5835
|
if (!node || typeof node !== "object") {
|
|
5160
5836
|
throw new NotFoundError(`NameSilo: no pricing found for TLD '${tld}'`);
|
|
5161
5837
|
}
|
|
5162
|
-
const
|
|
5838
|
+
const price2 = node;
|
|
5163
5839
|
return {
|
|
5164
5840
|
tld,
|
|
5165
5841
|
currency: "USD",
|
|
5166
|
-
registration: toPrice3(
|
|
5167
|
-
renewal: toPrice3(
|
|
5168
|
-
transfer: toPrice3(
|
|
5842
|
+
registration: toPrice3(price2.registration),
|
|
5843
|
+
renewal: toPrice3(price2.renew),
|
|
5844
|
+
transfer: toPrice3(price2.transfer)
|
|
5169
5845
|
};
|
|
5170
5846
|
}
|
|
5171
5847
|
/**
|
|
@@ -5249,7 +5925,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5249
5925
|
*/
|
|
5250
5926
|
async disableDnssec(domainName, opts) {
|
|
5251
5927
|
try {
|
|
5252
|
-
const res = await this.call("dnsSecListRecords", { domain: domainName }, opts);
|
|
5928
|
+
const res = await this.call("dnsSecListRecords", { domain: domainName }, asRead(opts));
|
|
5253
5929
|
if (!replyOk(res)) return statusResult2(res);
|
|
5254
5930
|
for (const d of ensureArray(res.reply?.ds_record)) {
|
|
5255
5931
|
const del = await this.call(
|
|
@@ -5322,7 +5998,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5322
5998
|
desired.set(f.alias, list);
|
|
5323
5999
|
}
|
|
5324
6000
|
const currentAliases = new Set(
|
|
5325
|
-
(await this.listEmailForwards(domainName, opts)).map((r) => r.alias).filter(Boolean)
|
|
6001
|
+
(await this.listEmailForwards(domainName, asRead(opts))).map((r) => r.alias).filter(Boolean)
|
|
5326
6002
|
);
|
|
5327
6003
|
for (const alias of currentAliases) {
|
|
5328
6004
|
if (!desired.has(alias)) {
|
|
@@ -5413,7 +6089,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5413
6089
|
* delegation nameservers aren't part of this list (see `updateNameservers`).
|
|
5414
6090
|
*/
|
|
5415
6091
|
async setDnsRecords(domainName, records, opts) {
|
|
5416
|
-
const listRes = await this.call("dnsListRecords", { domain: domainName }, opts);
|
|
6092
|
+
const listRes = await this.call("dnsListRecords", { domain: domainName }, asRead(opts));
|
|
5417
6093
|
if (!replyOk(listRes)) return { success: false, message: replyDetail(listRes) };
|
|
5418
6094
|
const existing = ensureArray(listRes.reply?.resource_record);
|
|
5419
6095
|
const existingByKey = /* @__PURE__ */ new Map();
|
|
@@ -5582,8 +6258,8 @@ function extractAvailEntries(group) {
|
|
|
5582
6258
|
const premiumRaw = o.premium ?? o["@premium"] ?? o["@_premium"];
|
|
5583
6259
|
const durationRaw = o.duration ?? o["@duration"] ?? o["@_duration"];
|
|
5584
6260
|
const entry = { name };
|
|
5585
|
-
const
|
|
5586
|
-
if (
|
|
6261
|
+
const price2 = toPrice3(priceRaw);
|
|
6262
|
+
if (price2 != null && price2 > 0) entry.price = price2;
|
|
5587
6263
|
if (premiumRaw != null && text(premiumRaw) !== "") entry.premium = isYes(premiumRaw);
|
|
5588
6264
|
const duration = toPrice3(durationRaw);
|
|
5589
6265
|
if (duration != null && duration > 0) entry.duration = duration;
|
|
@@ -5827,17 +6503,17 @@ var PorkbunRegistrar = class extends BaseRegistrar {
|
|
|
5827
6503
|
async tldPricing(tld, opts) {
|
|
5828
6504
|
const res = await this.call("/pricing/get", {}, opts);
|
|
5829
6505
|
if (!isOk3(res)) throw new Error(res.message ?? "API request failed");
|
|
5830
|
-
const
|
|
5831
|
-
if (!
|
|
6506
|
+
const price2 = res.pricing?.[tld];
|
|
6507
|
+
if (!price2) {
|
|
5832
6508
|
throw new NotFoundError(`${this.name}: no pricing found for TLD .${tld}`);
|
|
5833
6509
|
}
|
|
5834
6510
|
return {
|
|
5835
6511
|
tld,
|
|
5836
6512
|
currency: "USD",
|
|
5837
6513
|
// Porkbun prices are always quoted in USD
|
|
5838
|
-
registration: toPrice4(
|
|
5839
|
-
renewal: toPrice4(
|
|
5840
|
-
transfer: toPrice4(
|
|
6514
|
+
registration: toPrice4(price2.registration),
|
|
6515
|
+
renewal: toPrice4(price2.renewal),
|
|
6516
|
+
transfer: toPrice4(price2.transfer)
|
|
5841
6517
|
};
|
|
5842
6518
|
}
|
|
5843
6519
|
/**
|
|
@@ -5877,14 +6553,14 @@ var PorkbunRegistrar = class extends BaseRegistrar {
|
|
|
5877
6553
|
);
|
|
5878
6554
|
if (!isOk3(res)) throw new Error(res.message ?? "API request failed");
|
|
5879
6555
|
const info = res.response ?? {};
|
|
5880
|
-
const
|
|
6556
|
+
const price2 = toPrice4(info.price);
|
|
5881
6557
|
results.push({
|
|
5882
6558
|
domainName,
|
|
5883
6559
|
available: info.avail === "yes",
|
|
5884
6560
|
premium: info.premium === "yes",
|
|
5885
|
-
price,
|
|
5886
|
-
currency:
|
|
5887
|
-
period:
|
|
6561
|
+
price: price2,
|
|
6562
|
+
currency: price2 != null ? "USD" : void 0,
|
|
6563
|
+
period: price2 != null ? 1 : void 0
|
|
5888
6564
|
});
|
|
5889
6565
|
}
|
|
5890
6566
|
return results;
|
|
@@ -5940,7 +6616,7 @@ var PorkbunRegistrar = class extends BaseRegistrar {
|
|
|
5940
6616
|
* renewals aren't supported via the API.
|
|
5941
6617
|
*/
|
|
5942
6618
|
async renewDomain(domainName, _years = 1, opts) {
|
|
5943
|
-
const pricing = await this.tldPricing(extractTld(domainName), opts);
|
|
6619
|
+
const pricing = await this.tldPricing(extractTld(domainName), asRead(opts));
|
|
5944
6620
|
if (pricing.renewal == null) {
|
|
5945
6621
|
throw new Error(`${this.name}: could not determine the renewal price for ${domainName}`);
|
|
5946
6622
|
}
|
|
@@ -5971,7 +6647,7 @@ var PorkbunRegistrar = class extends BaseRegistrar {
|
|
|
5971
6647
|
* aren't supported via the API.
|
|
5972
6648
|
*/
|
|
5973
6649
|
async transferIn(domainName, input, opts) {
|
|
5974
|
-
const pricing = await this.tldPricing(extractTld(domainName), opts);
|
|
6650
|
+
const pricing = await this.tldPricing(extractTld(domainName), asRead(opts));
|
|
5975
6651
|
if (pricing.transfer == null) {
|
|
5976
6652
|
throw new Error(`${this.name}: could not determine the transfer price for ${domainName}`);
|
|
5977
6653
|
}
|
|
@@ -6264,6 +6940,8 @@ function toDnsRecord3(r, domainName) {
|
|
|
6264
6940
|
var WRITABLE_DNS_TYPES = /* @__PURE__ */ new Set(["A", "AAAA", "CNAME", "NS", "TXT", "MX"]);
|
|
6265
6941
|
var SpaceshipRegistrar = class extends BaseRegistrar {
|
|
6266
6942
|
name = "spaceship";
|
|
6943
|
+
// a REST API: GET and HEAD never change state
|
|
6944
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
6267
6945
|
static displayName = "Spaceship";
|
|
6268
6946
|
static website = "spaceship.com";
|
|
6269
6947
|
static helpText = 'Generate your API key and secret in the API Manager at Spaceship using the "New API key" button. The API requires both X-API-Key and X-API-Secret headers.';
|
|
@@ -6570,8 +7248,8 @@ var SpaceshipRegistrar = class extends BaseRegistrar {
|
|
|
6570
7248
|
...opts
|
|
6571
7249
|
});
|
|
6572
7250
|
}
|
|
6573
|
-
const keep = new Set(items.map((r) =>
|
|
6574
|
-
const stale = existing.filter((r) => (r.group?.type ?? "custom") === "custom").filter((r) => !keep.has(
|
|
7251
|
+
const keep = new Set(items.map((r) => recordKey2(r.type, r.name)));
|
|
7252
|
+
const stale = existing.filter((r) => (r.group?.type ?? "custom") === "custom").filter((r) => !keep.has(recordKey2(r.type, r.name))).map(({ group: _group, ...rr }) => rr);
|
|
6575
7253
|
if (stale.length > 0) {
|
|
6576
7254
|
await this.http.request({
|
|
6577
7255
|
method: "DELETE",
|
|
@@ -6730,7 +7408,7 @@ function toSpaceshipRecord(r) {
|
|
|
6730
7408
|
}
|
|
6731
7409
|
return record;
|
|
6732
7410
|
}
|
|
6733
|
-
function
|
|
7411
|
+
function recordKey2(type, name) {
|
|
6734
7412
|
return `${(type ?? "").toUpperCase()}\0${name ?? "@"}`;
|
|
6735
7413
|
}
|
|
6736
7414
|
function fromSpaceshipContact(c) {
|
|
@@ -6774,6 +7452,7 @@ var registrars = {
|
|
|
6774
7452
|
godaddy: GoDaddyRegistrar,
|
|
6775
7453
|
namebright: NameBrightRegistrar,
|
|
6776
7454
|
namecheap: NamecheapRegistrar,
|
|
7455
|
+
namecom: NamecomRegistrar,
|
|
6777
7456
|
namesilo: NameSiloRegistrar,
|
|
6778
7457
|
porkbun: PorkbunRegistrar,
|
|
6779
7458
|
spaceship: SpaceshipRegistrar
|
|
@@ -6783,6 +7462,6 @@ function createRegistrar(name, credentials, options) {
|
|
|
6783
7462
|
return new RegistrarClass(credentials, options);
|
|
6784
7463
|
}
|
|
6785
7464
|
|
|
6786
|
-
export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DynadotRegistrar, EXTENDED_FEATURES, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NotFoundError, NotImplementedError, ParsingError, PorkbunRegistrar, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, isCoreFeature, listPortfolio, normalizeDomain, normalizeNameservers, parseDate, parseXml, registrars, requireConsent, selectBaseUrl, settableForwards, sleep, toRegistrarError };
|
|
7465
|
+
export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, 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, isCoreFeature, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, registrars, requireConsent, selectBaseUrl, settableForwards, sleep, toRegistrarError, wasNotSent };
|
|
6787
7466
|
//# sourceMappingURL=index.js.map
|
|
6788
7467
|
//# sourceMappingURL=index.js.map
|