@aoxborrow/registrar-client 0.6.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 +64 -5
- package/dist/index.cjs +207 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +202 -19
- 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 +9 -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/namebright.ts +6 -2
- package/src/registrars/namecheap.ts +6 -1
- package/src/registrars/namecom.ts +10 -6
- 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/README.md
CHANGED
|
@@ -387,9 +387,9 @@ a provider-specific workflow. Transfers accept `authCode` and optional `privacy`
|
|
|
387
387
|
explicit `years`, `contacts` or `autoRenew` are rejected before submission because
|
|
388
388
|
Core cannot apply them in its transfer request. Update settings after completion.
|
|
389
389
|
|
|
390
|
-
|
|
391
|
-
Transport/parse failures after a purchase report an
|
|
392
|
-
Name.com orders and domain/transfer status before retrying. An accepted order is
|
|
390
|
+
Mutations follow the shared [retry rules](#retries): a request Name.com may have
|
|
391
|
+
received is never re-sent. Transport/parse failures after a purchase report an
|
|
392
|
+
unknown outcome: inspect Name.com orders and domain/transfer status before retrying. An accepted order is
|
|
393
393
|
reported as accepted, not proof of final registry completion. Read operations
|
|
394
394
|
retain the shared retry policy and honor `Retry-After` / `X-RateLimit-Reset`.
|
|
395
395
|
HTTP errors retain typed status information but omit provider response bodies,
|
|
@@ -482,8 +482,12 @@ implementations**:
|
|
|
482
482
|
Extend `BaseRegistrar`, pass a `baseUrl` + auth headers to `super()`, add the
|
|
483
483
|
static `displayName` / `configFields` / `helpText` metadata, override the
|
|
484
484
|
operations the API supports (mapping payloads to the shared types in
|
|
485
|
-
`src/types.ts`), and add the class to `src/registrars/index.ts`.
|
|
486
|
-
|
|
485
|
+
`src/types.ts`), and add the class to `src/registrars/index.ts`. Forward the
|
|
486
|
+
`opts` you are given into every `this.http` request: they carry the timeout and
|
|
487
|
+
signal, and tell the [retry rules](#retries) whether the call reads or writes.
|
|
488
|
+
For a REST API set `static safeMethods = REST_SAFE_METHODS`; otherwise wrap any
|
|
489
|
+
lookup a write performs first in `asRead(opts)`. The existing providers under
|
|
490
|
+
`src/registrars/` are working references.
|
|
487
491
|
|
|
488
492
|
**New providers are welcome** — if you've wired up a registrar, open a PR.
|
|
489
493
|
|
|
@@ -493,6 +497,61 @@ All failures throw a typed subclass of `RegistrarError` (`AuthenticationError`,
|
|
|
493
497
|
`NotFoundError`, `RateLimitError`, `TimeoutError`, `NotImplementedError`, …),
|
|
494
498
|
each carrying an HTTP-style `status`.
|
|
495
499
|
|
|
500
|
+
## Retries
|
|
501
|
+
|
|
502
|
+
Requests retry with exponential backoff (`retries`, default 2). What may be
|
|
503
|
+
re-sent depends on where the failure happened and on whether the call reads or
|
|
504
|
+
writes, and is the same for every provider:
|
|
505
|
+
|
|
506
|
+
| Failure | Read | Write |
|
|
507
|
+
| -------------------------------------------------------------------------- | ----- | -------------------------------------- |
|
|
508
|
+
| The registrar never saw it: DNS failure, connection refused, TLS handshake | Retry | Retry |
|
|
509
|
+
| `429`, honoring `Retry-After` | Retry | Retry |
|
|
510
|
+
| Outcome unknown: timeout, connection dropped mid-response, `5xx` | Retry | **Not re-sent.** `OutcomeUnknownError` |
|
|
511
|
+
| Any other response | Fail | Fail |
|
|
512
|
+
|
|
513
|
+
Reads and writes are told apart by feature, not by HTTP method (`FEATURE_CALLS`
|
|
514
|
+
lists every one). The method is no guide: Namecheap sends writes as `GET`, and
|
|
515
|
+
Porkbun sends reads as `POST`. `getAuthCode` counts as a write because some
|
|
516
|
+
registrars regenerate the code.
|
|
517
|
+
|
|
518
|
+
A write often reads first: current DNS records, a price, a zone id. Those
|
|
519
|
+
lookups changed nothing, so they are retried like any read and never reported as
|
|
520
|
+
an unknown outcome. REST providers get this from the HTTP method
|
|
521
|
+
(`static safeMethods = REST_SAFE_METHODS` treats `GET` and `HEAD` as reads);
|
|
522
|
+
providers whose method means nothing wrap the lookup's options in `asRead(opts)`.
|
|
523
|
+
|
|
524
|
+
A write whose outcome is unknown may have been applied, and a second renewal
|
|
525
|
+
charges twice. So the library reports it and leaves the decision to you: re-read
|
|
526
|
+
the domain, then retry if nothing changed. Providers that throw raise
|
|
527
|
+
`OutcomeUnknownError`; providers that return an `OperationResult` set
|
|
528
|
+
`outcome: 'unknown'` on it:
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
const result = await client.renewDomain('example.com', 1);
|
|
532
|
+
if (!result.success && result.outcome === 'unknown') {
|
|
533
|
+
const domain = await client.getDomain('example.com'); // did it go through?
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Only positive evidence counts as "never sent": a pre-connection error code from
|
|
538
|
+
Node's `fetch`, or an error a custom transport marked with `markNotSent`.
|
|
539
|
+
Browsers and Workers report network failures opaquely, so there they count as
|
|
540
|
+
unknown.
|
|
541
|
+
|
|
542
|
+
### Custom transport
|
|
543
|
+
|
|
544
|
+
Pass `fetch` to route every request through your own transport, such as a
|
|
545
|
+
proxy:
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
const registrar = createRegistrar('namecheap', credentials, { fetch: proxiedFetch });
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
If the transport can fail before the request leaves, for instance a proxy that
|
|
552
|
+
refuses the tunnel, throw `markNotSent(error)` so the failure is retried even
|
|
553
|
+
for writes.
|
|
554
|
+
|
|
496
555
|
## Security
|
|
497
556
|
|
|
498
557
|
Credentials are sent as request **headers** wherever the registrar's API
|
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;
|
|
63
69
|
}
|
|
64
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;
|
|
81
|
+
}
|
|
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.';
|
|
@@ -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.";
|
|
@@ -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.)";
|
|
@@ -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");
|
|
@@ -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`);
|
|
@@ -4979,7 +5156,7 @@ var NamecomHttpClient = class extends HttpClient {
|
|
|
4979
5156
|
} catch (error) {
|
|
4980
5157
|
if (error instanceof ParsingError) throw new ParsingError("namecom: invalid JSON response");
|
|
4981
5158
|
if (error instanceof ConnectionError)
|
|
4982
|
-
throw new ConnectionError("namecom: network request failed");
|
|
5159
|
+
throw new ConnectionError("namecom: network request failed", { notSent: error.notSent });
|
|
4983
5160
|
throw error;
|
|
4984
5161
|
}
|
|
4985
5162
|
}
|
|
@@ -5004,6 +5181,8 @@ var NamecomHttpClient = class extends HttpClient {
|
|
|
5004
5181
|
};
|
|
5005
5182
|
var NamecomRegistrar = class extends BaseRegistrar {
|
|
5006
5183
|
name = "namecom";
|
|
5184
|
+
// a REST API: GET and HEAD never change state
|
|
5185
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
5007
5186
|
static displayName = "Name.com";
|
|
5008
5187
|
static website = "name.com";
|
|
5009
5188
|
static supportsSandbox = true;
|
|
@@ -5032,7 +5211,11 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5032
5211
|
headers: { Authorization: `Basic ${auth}` }
|
|
5033
5212
|
};
|
|
5034
5213
|
super(credentials, config, options);
|
|
5035
|
-
this.http = new NamecomHttpClient({
|
|
5214
|
+
this.http = new NamecomHttpClient({
|
|
5215
|
+
...config,
|
|
5216
|
+
safeMethods: REST_SAFE_METHODS,
|
|
5217
|
+
options: this.options
|
|
5218
|
+
});
|
|
5036
5219
|
}
|
|
5037
5220
|
async testConnection(opts) {
|
|
5038
5221
|
try {
|
|
@@ -5260,7 +5443,6 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5260
5443
|
const old = index >= 0 ? remaining.splice(index, 1)[0] : void 0;
|
|
5261
5444
|
await this.http.request({
|
|
5262
5445
|
...opts,
|
|
5263
|
-
retries: 0,
|
|
5264
5446
|
method: old ? "PUT" : "POST",
|
|
5265
5447
|
path: old ? `${path}/${old.id}` : path,
|
|
5266
5448
|
body: record
|
|
@@ -5269,7 +5451,6 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5269
5451
|
for (const old of remaining) {
|
|
5270
5452
|
await this.http.request({
|
|
5271
5453
|
...opts,
|
|
5272
|
-
retries: 0,
|
|
5273
5454
|
method: "DELETE",
|
|
5274
5455
|
path: `${path}/${old.id}`
|
|
5275
5456
|
});
|
|
@@ -5333,7 +5514,7 @@ var NamecomRegistrar = class extends BaseRegistrar {
|
|
|
5333
5514
|
}
|
|
5334
5515
|
async mutate(req, opts) {
|
|
5335
5516
|
try {
|
|
5336
|
-
await this.http.request({ ...req, ...opts
|
|
5517
|
+
await this.http.request({ ...req, ...opts });
|
|
5337
5518
|
return { success: true, message: "Domain updated successfully" };
|
|
5338
5519
|
} catch (error) {
|
|
5339
5520
|
return { success: false, message: toRegistrarError(error).message };
|
|
@@ -5746,7 +5927,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5746
5927
|
*/
|
|
5747
5928
|
async disableDnssec(domainName, opts) {
|
|
5748
5929
|
try {
|
|
5749
|
-
const res = await this.call("dnsSecListRecords", { domain: domainName }, opts);
|
|
5930
|
+
const res = await this.call("dnsSecListRecords", { domain: domainName }, asRead(opts));
|
|
5750
5931
|
if (!replyOk(res)) return statusResult2(res);
|
|
5751
5932
|
for (const d of ensureArray(res.reply?.ds_record)) {
|
|
5752
5933
|
const del = await this.call(
|
|
@@ -5819,7 +6000,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5819
6000
|
desired.set(f.alias, list);
|
|
5820
6001
|
}
|
|
5821
6002
|
const currentAliases = new Set(
|
|
5822
|
-
(await this.listEmailForwards(domainName, opts)).map((r) => r.alias).filter(Boolean)
|
|
6003
|
+
(await this.listEmailForwards(domainName, asRead(opts))).map((r) => r.alias).filter(Boolean)
|
|
5823
6004
|
);
|
|
5824
6005
|
for (const alias of currentAliases) {
|
|
5825
6006
|
if (!desired.has(alias)) {
|
|
@@ -5910,7 +6091,7 @@ var NameSiloRegistrar = class extends BaseRegistrar {
|
|
|
5910
6091
|
* delegation nameservers aren't part of this list (see `updateNameservers`).
|
|
5911
6092
|
*/
|
|
5912
6093
|
async setDnsRecords(domainName, records, opts) {
|
|
5913
|
-
const listRes = await this.call("dnsListRecords", { domain: domainName }, opts);
|
|
6094
|
+
const listRes = await this.call("dnsListRecords", { domain: domainName }, asRead(opts));
|
|
5914
6095
|
if (!replyOk(listRes)) return { success: false, message: replyDetail(listRes) };
|
|
5915
6096
|
const existing = ensureArray(listRes.reply?.resource_record);
|
|
5916
6097
|
const existingByKey = /* @__PURE__ */ new Map();
|
|
@@ -6437,7 +6618,7 @@ var PorkbunRegistrar = class extends BaseRegistrar {
|
|
|
6437
6618
|
* renewals aren't supported via the API.
|
|
6438
6619
|
*/
|
|
6439
6620
|
async renewDomain(domainName, _years = 1, opts) {
|
|
6440
|
-
const pricing = await this.tldPricing(extractTld(domainName), opts);
|
|
6621
|
+
const pricing = await this.tldPricing(extractTld(domainName), asRead(opts));
|
|
6441
6622
|
if (pricing.renewal == null) {
|
|
6442
6623
|
throw new Error(`${this.name}: could not determine the renewal price for ${domainName}`);
|
|
6443
6624
|
}
|
|
@@ -6468,7 +6649,7 @@ var PorkbunRegistrar = class extends BaseRegistrar {
|
|
|
6468
6649
|
* aren't supported via the API.
|
|
6469
6650
|
*/
|
|
6470
6651
|
async transferIn(domainName, input, opts) {
|
|
6471
|
-
const pricing = await this.tldPricing(extractTld(domainName), opts);
|
|
6652
|
+
const pricing = await this.tldPricing(extractTld(domainName), asRead(opts));
|
|
6472
6653
|
if (pricing.transfer == null) {
|
|
6473
6654
|
throw new Error(`${this.name}: could not determine the transfer price for ${domainName}`);
|
|
6474
6655
|
}
|
|
@@ -6761,6 +6942,8 @@ function toDnsRecord3(r, domainName) {
|
|
|
6761
6942
|
var WRITABLE_DNS_TYPES = /* @__PURE__ */ new Set(["A", "AAAA", "CNAME", "NS", "TXT", "MX"]);
|
|
6762
6943
|
var SpaceshipRegistrar = class extends BaseRegistrar {
|
|
6763
6944
|
name = "spaceship";
|
|
6945
|
+
// a REST API: GET and HEAD never change state
|
|
6946
|
+
static safeMethods = REST_SAFE_METHODS;
|
|
6764
6947
|
static displayName = "Spaceship";
|
|
6765
6948
|
static website = "spaceship.com";
|
|
6766
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.';
|
|
@@ -7294,6 +7477,7 @@ exports.ConsentRequiredError = ConsentRequiredError;
|
|
|
7294
7477
|
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
7295
7478
|
exports.DynadotRegistrar = DynadotRegistrar;
|
|
7296
7479
|
exports.EXTENDED_FEATURES = EXTENDED_FEATURES;
|
|
7480
|
+
exports.FEATURE_CALLS = FEATURE_CALLS;
|
|
7297
7481
|
exports.Feature = Feature;
|
|
7298
7482
|
exports.GandiRegistrar = GandiRegistrar;
|
|
7299
7483
|
exports.GoDaddyRegistrar = GoDaddyRegistrar;
|
|
@@ -7306,8 +7490,10 @@ exports.NamecheapRegistrar = NamecheapRegistrar;
|
|
|
7306
7490
|
exports.NamecomRegistrar = NamecomRegistrar;
|
|
7307
7491
|
exports.NotFoundError = NotFoundError;
|
|
7308
7492
|
exports.NotImplementedError = NotImplementedError;
|
|
7493
|
+
exports.OutcomeUnknownError = OutcomeUnknownError;
|
|
7309
7494
|
exports.ParsingError = ParsingError;
|
|
7310
7495
|
exports.PorkbunRegistrar = PorkbunRegistrar;
|
|
7496
|
+
exports.REST_SAFE_METHODS = REST_SAFE_METHODS;
|
|
7311
7497
|
exports.RateLimitError = RateLimitError;
|
|
7312
7498
|
exports.RegistrarClient = RegistrarClient;
|
|
7313
7499
|
exports.RegistrarError = RegistrarError;
|
|
@@ -7315,6 +7501,7 @@ exports.RetryableRegistrarError = RetryableRegistrarError;
|
|
|
7315
7501
|
exports.SpaceshipRegistrar = SpaceshipRegistrar;
|
|
7316
7502
|
exports.TimeoutError = TimeoutError;
|
|
7317
7503
|
exports.USER_AGENT = USER_AGENT;
|
|
7504
|
+
exports.asRead = asRead;
|
|
7318
7505
|
exports.backoffDelay = backoffDelay;
|
|
7319
7506
|
exports.createDomain = createDomain;
|
|
7320
7507
|
exports.createRegistrar = createRegistrar;
|
|
@@ -7322,6 +7509,7 @@ exports.ensureArray = ensureArray;
|
|
|
7322
7509
|
exports.filterDomains = filterDomains;
|
|
7323
7510
|
exports.isCoreFeature = isCoreFeature;
|
|
7324
7511
|
exports.listPortfolio = listPortfolio;
|
|
7512
|
+
exports.markNotSent = markNotSent;
|
|
7325
7513
|
exports.normalizeDomain = normalizeDomain;
|
|
7326
7514
|
exports.normalizeNameservers = normalizeNameservers;
|
|
7327
7515
|
exports.parseDate = parseDate;
|
|
@@ -7332,5 +7520,6 @@ exports.selectBaseUrl = selectBaseUrl;
|
|
|
7332
7520
|
exports.settableForwards = settableForwards;
|
|
7333
7521
|
exports.sleep = sleep;
|
|
7334
7522
|
exports.toRegistrarError = toRegistrarError;
|
|
7523
|
+
exports.wasNotSent = wasNotSent;
|
|
7335
7524
|
//# sourceMappingURL=index.cjs.map
|
|
7336
7525
|
//# sourceMappingURL=index.cjs.map
|