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