@aoxborrow/registrar-client 0.1.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/LICENSE +21 -0
- package/README.md +415 -0
- package/dist/index.cjs +6798 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +768 -0
- package/dist/index.d.ts +768 -0
- package/dist/index.js +6747 -0
- package/dist/index.js.map +1 -0
- package/package.json +110 -0
- package/src/client.ts +200 -0
- package/src/constants.ts +11 -0
- package/src/errors.ts +192 -0
- package/src/features.ts +104 -0
- package/src/http.ts +230 -0
- package/src/index.ts +39 -0
- package/src/portfolio.ts +65 -0
- package/src/registrar.ts +240 -0
- package/src/registrars/cloudflare.ts +1205 -0
- package/src/registrars/dynadot.ts +1097 -0
- package/src/registrars/gandi.ts +970 -0
- package/src/registrars/godaddy.ts +1342 -0
- package/src/registrars/index.ts +41 -0
- package/src/registrars/namebright.ts +775 -0
- package/src/registrars/namecheap.ts +870 -0
- package/src/registrars/namesilo.ts +997 -0
- package/src/registrars/porkbun.ts +930 -0
- package/src/registrars/spaceship.ts +736 -0
- package/src/types.ts +446 -0
- package/src/utils.ts +135 -0
- package/src/xml.ts +39 -0
package/src/http.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { USER_AGENT } from './constants';
|
|
2
|
+
import {
|
|
3
|
+
AbortError,
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
AuthorizationError,
|
|
6
|
+
ConnectionError,
|
|
7
|
+
InvalidResponseError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
ParsingError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
RegistrarError,
|
|
12
|
+
TimeoutError,
|
|
13
|
+
toRegistrarError,
|
|
14
|
+
} from './errors';
|
|
15
|
+
import type { RegistrarClientOptions } from './types';
|
|
16
|
+
import { backoffDelay, sleep } from './utils';
|
|
17
|
+
|
|
18
|
+
// configuration for an HttpClient instance
|
|
19
|
+
export interface HttpClientConfig {
|
|
20
|
+
// base URL for all requests, e.g. "https://api.example.com/v2"
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
// default headers applied to every request (e.g. Authorization) — never logged
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
// request/retry behavior
|
|
25
|
+
options: RegistrarClientOptions;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// options for a single request
|
|
29
|
+
export interface RequestConfig {
|
|
30
|
+
method?: string;
|
|
31
|
+
// path appended to baseUrl, or an absolute URL
|
|
32
|
+
path: string;
|
|
33
|
+
// query parameters (never used for secrets)
|
|
34
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
35
|
+
// JSON body; serialized with JSON.stringify
|
|
36
|
+
body?: unknown;
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
// per-request overrides
|
|
39
|
+
timeout?: number;
|
|
40
|
+
retries?: number;
|
|
41
|
+
backoff?: number;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// a small, browser/edge-safe fetch wrapper with timeout, abort linking,
|
|
46
|
+
// retry-with-backoff, and registrar error mapping. No Node built-ins.
|
|
47
|
+
export class HttpClient {
|
|
48
|
+
constructor(protected config: HttpClientConfig) {}
|
|
49
|
+
|
|
50
|
+
// perform a request and parse a JSON response body
|
|
51
|
+
async request<T = unknown>(req: RequestConfig): Promise<T> {
|
|
52
|
+
return this.withRetries(req, async () => {
|
|
53
|
+
const text = await this.send(req);
|
|
54
|
+
if (text.length === 0) return undefined as T;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(text) as T;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new ParsingError(
|
|
59
|
+
`Failed to parse JSON response from '${this.buildUrl(req)}': ${errorMessage(error)}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// perform a request and return the raw response body text (e.g. XML APIs)
|
|
66
|
+
async requestText(req: RequestConfig): Promise<string> {
|
|
67
|
+
return this.withRetries(req, () => this.send(req));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// shared retry-with-backoff wrapper around a single-attempt operation
|
|
71
|
+
protected async withRetries<T>(req: RequestConfig, attemptFn: () => Promise<T>): Promise<T> {
|
|
72
|
+
const retries = req.retries ?? this.config.options.retries;
|
|
73
|
+
const backoff = req.backoff ?? this.config.options.backoff;
|
|
74
|
+
|
|
75
|
+
let lastError: RegistrarError | undefined;
|
|
76
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
77
|
+
try {
|
|
78
|
+
return await attemptFn();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
lastError = toRegistrarError(error);
|
|
81
|
+
|
|
82
|
+
// out of attempts, or the error is not retryable
|
|
83
|
+
if (attempt >= retries || !lastError.shouldRetry()) {
|
|
84
|
+
throw lastError;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// honor Retry-After for rate limits when present, else exponential backoff
|
|
88
|
+
const delay =
|
|
89
|
+
lastError instanceof RateLimitError && lastError.retryAfter != null
|
|
90
|
+
? lastError.retryAfter * 1000
|
|
91
|
+
: backoffDelay(backoff, attempt);
|
|
92
|
+
await sleep(delay);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// unreachable: the loop always returns or throws
|
|
97
|
+
throw lastError ?? new RegistrarError('Request failed');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// build the full URL for a request, applying query parameters
|
|
101
|
+
protected buildUrl(req: RequestConfig): string {
|
|
102
|
+
let base: string;
|
|
103
|
+
if (/^https?:\/\//.test(req.path)) {
|
|
104
|
+
// absolute URL
|
|
105
|
+
base = req.path;
|
|
106
|
+
} else if (req.path === '') {
|
|
107
|
+
// empty path: the baseUrl is itself the full endpoint (e.g. Namecheap/Dynadot)
|
|
108
|
+
base = this.config.baseUrl;
|
|
109
|
+
} else {
|
|
110
|
+
base = `${this.config.baseUrl.replace(/\/$/, '')}/${req.path.replace(/^\//, '')}`;
|
|
111
|
+
}
|
|
112
|
+
const url = new URL(base);
|
|
113
|
+
if (req.query) {
|
|
114
|
+
for (const [key, value] of Object.entries(req.query)) {
|
|
115
|
+
if (value !== undefined) url.searchParams.set(key, String(value));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return url.toString();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// send a single request (no retries), map errors, and return the raw body text
|
|
122
|
+
protected async send(req: RequestConfig): Promise<string> {
|
|
123
|
+
const timeout = req.timeout ?? this.config.options.timeout;
|
|
124
|
+
const externalSignal = req.signal ?? this.config.options.signal;
|
|
125
|
+
|
|
126
|
+
// abort controller for this request, linked to any external signal
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
if (externalSignal?.aborted) {
|
|
129
|
+
throw new AbortError('Request was aborted');
|
|
130
|
+
}
|
|
131
|
+
const onAbort = () => controller.abort();
|
|
132
|
+
externalSignal?.addEventListener('abort', onAbort);
|
|
133
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
134
|
+
|
|
135
|
+
const url = this.buildUrl(req);
|
|
136
|
+
const headers: Record<string, string> = {
|
|
137
|
+
'User-Agent': USER_AGENT,
|
|
138
|
+
'Accept': 'application/json',
|
|
139
|
+
...this.config.headers,
|
|
140
|
+
...req.headers,
|
|
141
|
+
};
|
|
142
|
+
if (req.body !== undefined && headers['Content-Type'] === undefined) {
|
|
143
|
+
headers['Content-Type'] = 'application/json';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
let response: Response;
|
|
148
|
+
try {
|
|
149
|
+
response = await fetch(url, {
|
|
150
|
+
method: req.method ?? 'GET',
|
|
151
|
+
headers,
|
|
152
|
+
body: req.body !== undefined ? JSON.stringify(req.body) : undefined,
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// distinguish abort/timeout from a genuine network failure
|
|
157
|
+
if (isAbortError(error)) {
|
|
158
|
+
if (externalSignal?.aborted) throw new AbortError('Request was aborted');
|
|
159
|
+
throw new TimeoutError(`Request to '${url}' timed out after ${timeout}ms`);
|
|
160
|
+
}
|
|
161
|
+
throw new ConnectionError(`Failed to reach '${url}': ${errorMessage(error)}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw await this.toStatusError(response, url);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 204 No Content yields an empty string
|
|
169
|
+
if (response.status === 204) {
|
|
170
|
+
return '';
|
|
171
|
+
}
|
|
172
|
+
return await response.text();
|
|
173
|
+
} finally {
|
|
174
|
+
clearTimeout(timeoutId);
|
|
175
|
+
externalSignal?.removeEventListener('abort', onAbort);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// map a non-2xx response to a typed RegistrarError
|
|
180
|
+
protected async toStatusError(response: Response, url: string): Promise<RegistrarError> {
|
|
181
|
+
// read the body defensively; it may aid debugging but must not be assumed JSON
|
|
182
|
+
let detail = '';
|
|
183
|
+
try {
|
|
184
|
+
detail = (await response.text()).slice(0, 500);
|
|
185
|
+
} catch {
|
|
186
|
+
// ignore body read failures
|
|
187
|
+
}
|
|
188
|
+
const message = `Request to '${url}' failed with ${response.status} ${response.statusText}${
|
|
189
|
+
detail ? `: ${detail}` : ''
|
|
190
|
+
}`;
|
|
191
|
+
|
|
192
|
+
switch (response.status) {
|
|
193
|
+
case 401:
|
|
194
|
+
return new AuthenticationError(message);
|
|
195
|
+
case 403:
|
|
196
|
+
return new AuthorizationError(message);
|
|
197
|
+
case 404:
|
|
198
|
+
return new NotFoundError(message);
|
|
199
|
+
case 429: {
|
|
200
|
+
const retryAfter = Number(response.headers.get('retry-after'));
|
|
201
|
+
return new RateLimitError(message, Number.isFinite(retryAfter) ? retryAfter : undefined);
|
|
202
|
+
}
|
|
203
|
+
default: {
|
|
204
|
+
if (response.status >= 500) {
|
|
205
|
+
return new InvalidResponseError(message);
|
|
206
|
+
}
|
|
207
|
+
// preserve the real status for 4xx codes without a dedicated class
|
|
208
|
+
// (400, 402, 409, 422, …) so callers can branch on it
|
|
209
|
+
const err = new RegistrarError(message);
|
|
210
|
+
err.status = response.status;
|
|
211
|
+
return err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// detect an AbortController/fetch abort across environments
|
|
218
|
+
function isAbortError(error: unknown): boolean {
|
|
219
|
+
return (
|
|
220
|
+
typeof error === 'object' &&
|
|
221
|
+
error !== null &&
|
|
222
|
+
'name' in error &&
|
|
223
|
+
(error as { name?: string }).name === 'AbortError'
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// safely extract a message from an unknown thrown value
|
|
228
|
+
function errorMessage(error: unknown): string {
|
|
229
|
+
return error instanceof Error ? error.message : String(error);
|
|
230
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Public API surface.
|
|
2
|
+
|
|
3
|
+
export { RegistrarClient } from './client';
|
|
4
|
+
export { listPortfolio } from './portfolio';
|
|
5
|
+
export type { DomainSource, PortfolioError, PortfolioResult } from './portfolio';
|
|
6
|
+
export { HttpClient } from './http';
|
|
7
|
+
export type { HttpClientConfig, RequestConfig } from './http';
|
|
8
|
+
|
|
9
|
+
// core registrar abstraction (the `Registrar` interface itself is exported via
|
|
10
|
+
// `export * from './types'` below)
|
|
11
|
+
export { BaseRegistrar, selectBaseUrl } from './registrar';
|
|
12
|
+
|
|
13
|
+
// built-in providers lookup + factory
|
|
14
|
+
export { registrars, createRegistrar } from './registrars/index';
|
|
15
|
+
export type { RegistrarName } from './registrars/index';
|
|
16
|
+
|
|
17
|
+
// capability model: feature constants, the core contract, and helpers
|
|
18
|
+
export { Feature, CORE_FEATURES, EXTENDED_FEATURES, ALL_FEATURES, isCoreFeature } from './features';
|
|
19
|
+
export type { RegistrarFeature } from './features';
|
|
20
|
+
|
|
21
|
+
// bundled providers
|
|
22
|
+
export { CloudflareRegistrar } from './registrars/cloudflare';
|
|
23
|
+
export { DynadotRegistrar } from './registrars/dynadot';
|
|
24
|
+
export { GandiRegistrar } from './registrars/gandi';
|
|
25
|
+
export { GoDaddyRegistrar } from './registrars/godaddy';
|
|
26
|
+
export { NameBrightRegistrar } from './registrars/namebright';
|
|
27
|
+
export { NamecheapRegistrar } from './registrars/namecheap';
|
|
28
|
+
export { NameSiloRegistrar } from './registrars/namesilo';
|
|
29
|
+
export { PorkbunRegistrar } from './registrars/porkbun';
|
|
30
|
+
export { SpaceshipRegistrar } from './registrars/spaceship';
|
|
31
|
+
|
|
32
|
+
// XML parsing helpers for XML-based registrar APIs
|
|
33
|
+
export { parseXml, ensureArray } from './xml';
|
|
34
|
+
|
|
35
|
+
// shared types, constants, utils, and errors
|
|
36
|
+
export * from './types';
|
|
37
|
+
export * from './constants';
|
|
38
|
+
export * from './utils';
|
|
39
|
+
export * from './errors';
|
package/src/portfolio.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Aggregate domains across multiple registrars into one combined portfolio.
|
|
2
|
+
//
|
|
3
|
+
// Each source is queried concurrently and failures are isolated per-registrar
|
|
4
|
+
// (one provider being down never sinks the whole view). Every returned Domain
|
|
5
|
+
// already carries its `registrar`, so the flattened list stays attributable.
|
|
6
|
+
|
|
7
|
+
import type { RegistrarClient } from './client';
|
|
8
|
+
import type { Domain, ListDomainsOptions, Registrar } from './types';
|
|
9
|
+
import { toRegistrarError } from './errors';
|
|
10
|
+
|
|
11
|
+
// anything that can list domains: a raw Registrar or the RegistrarClient facade
|
|
12
|
+
export type DomainSource = Registrar | RegistrarClient;
|
|
13
|
+
|
|
14
|
+
// a per-registrar failure from a portfolio fetch
|
|
15
|
+
export interface PortfolioError {
|
|
16
|
+
// the registrar id that failed, e.g. "godaddy"
|
|
17
|
+
registrar: string;
|
|
18
|
+
error: Error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// the outcome of a portfolio fetch: all domains that loaded, plus any errors
|
|
22
|
+
export interface PortfolioResult {
|
|
23
|
+
domains: Domain[];
|
|
24
|
+
errors: PortfolioError[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// resolve a source's registrar id, whether it's a client facade or a provider
|
|
28
|
+
function sourceName(source: DomainSource): string {
|
|
29
|
+
return 'provider' in source ? source.provider.name : source.name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List domains across many registrars at once. Queries every source in parallel
|
|
34
|
+
* and collects results with per-registrar error isolation.
|
|
35
|
+
*
|
|
36
|
+
* `opts` (including `search`) is passed to EACH source. Every source returns its
|
|
37
|
+
* full account; order of the returned `domains` follows the order of `sources`.
|
|
38
|
+
*
|
|
39
|
+
* const { domains, errors } = await listPortfolio(
|
|
40
|
+
* [godaddyClient, namecheapClient, gandiClient],
|
|
41
|
+
* { search: 'acme' }
|
|
42
|
+
* );
|
|
43
|
+
*/
|
|
44
|
+
export async function listPortfolio(
|
|
45
|
+
sources: DomainSource[],
|
|
46
|
+
opts?: ListDomainsOptions
|
|
47
|
+
): Promise<PortfolioResult> {
|
|
48
|
+
const settled = await Promise.allSettled(sources.map(source => source.listDomains(opts)));
|
|
49
|
+
|
|
50
|
+
const domains: Domain[] = [];
|
|
51
|
+
const errors: PortfolioError[] = [];
|
|
52
|
+
|
|
53
|
+
settled.forEach((result, i) => {
|
|
54
|
+
if (result.status === 'fulfilled') {
|
|
55
|
+
domains.push(...result.value);
|
|
56
|
+
} else {
|
|
57
|
+
errors.push({
|
|
58
|
+
registrar: sourceName(sources[i]),
|
|
59
|
+
error: toRegistrarError(result.reason),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return { domains, errors };
|
|
65
|
+
}
|
package/src/registrar.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { DEFAULT_OPTIONS } from './constants';
|
|
2
|
+
import { ConfigurationError, NotImplementedError } from './errors';
|
|
3
|
+
import { HttpClient, type HttpClientConfig } from './http';
|
|
4
|
+
import { CORE_FEATURES, type RegistrarFeature } from './features';
|
|
5
|
+
import type {
|
|
6
|
+
ConnectionResult,
|
|
7
|
+
ContactSet,
|
|
8
|
+
DnsRecord,
|
|
9
|
+
Domain,
|
|
10
|
+
DomainAvailability,
|
|
11
|
+
DomainForward,
|
|
12
|
+
DnssecStatus,
|
|
13
|
+
EmailForward,
|
|
14
|
+
ListDomainsOptions,
|
|
15
|
+
OperationResult,
|
|
16
|
+
Registrar,
|
|
17
|
+
RegisterDomainInput,
|
|
18
|
+
RegistrarClientOptions,
|
|
19
|
+
RegistrarCredentials,
|
|
20
|
+
RegistrarEnvironment,
|
|
21
|
+
RegistrarOptions,
|
|
22
|
+
RequestOptions,
|
|
23
|
+
TldPricing,
|
|
24
|
+
TransferDomainInput,
|
|
25
|
+
} from './types';
|
|
26
|
+
|
|
27
|
+
// pick the base URL for the requested environment. Throws if `sandbox` is
|
|
28
|
+
// requested but the provider defines no sandbox URL — so integration tests
|
|
29
|
+
// fail loudly rather than silently hitting production.
|
|
30
|
+
export function selectBaseUrl(
|
|
31
|
+
registrar: string,
|
|
32
|
+
environment: RegistrarEnvironment | undefined,
|
|
33
|
+
urls: { production: string; sandbox?: string }
|
|
34
|
+
): string {
|
|
35
|
+
if (environment === 'sandbox') {
|
|
36
|
+
if (!urls.sandbox) {
|
|
37
|
+
throw new ConfigurationError(`${registrar} does not provide a sandbox environment`);
|
|
38
|
+
}
|
|
39
|
+
return urls.sandbox;
|
|
40
|
+
}
|
|
41
|
+
return urls.production;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Abstract base for registrar providers. Owns the shared HTTP/auth/retry
|
|
45
|
+
// plumbing (via `HttpClient`) and provides `NotImplementedError`-rejecting
|
|
46
|
+
// defaults for every operation. Concrete providers extend this, build their
|
|
47
|
+
// base URL + auth from the credentials passed to `super()`, and override the
|
|
48
|
+
// operations their API supports.
|
|
49
|
+
export abstract class BaseRegistrar implements Registrar {
|
|
50
|
+
abstract readonly name: string;
|
|
51
|
+
|
|
52
|
+
protected http: HttpClient;
|
|
53
|
+
protected credentials: RegistrarCredentials;
|
|
54
|
+
protected options: RegistrarClientOptions;
|
|
55
|
+
readonly environment: RegistrarEnvironment;
|
|
56
|
+
|
|
57
|
+
// Extended capabilities this provider opts into, beyond the core contract.
|
|
58
|
+
// Subclasses override this; core features are inherited and not re-declared.
|
|
59
|
+
static readonly extendedFeatures: readonly RegistrarFeature[] = [];
|
|
60
|
+
|
|
61
|
+
// The provider's full capability set: the core contract plus its extended
|
|
62
|
+
// features. Available statically (no instance/credentials needed) for catalog
|
|
63
|
+
// UIs and MCP tooling.
|
|
64
|
+
static get features(): readonly RegistrarFeature[] {
|
|
65
|
+
return [...CORE_FEATURES, ...this.extendedFeatures];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// instance mirror of the static `features`
|
|
69
|
+
get features(): readonly RegistrarFeature[] {
|
|
70
|
+
return (this.constructor as typeof BaseRegistrar).features;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// whether this provider supports a given capability
|
|
74
|
+
supports(feature: RegistrarFeature): boolean {
|
|
75
|
+
return this.features.includes(feature);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
credentials: RegistrarCredentials,
|
|
80
|
+
httpConfig: Omit<HttpClientConfig, 'options'>,
|
|
81
|
+
options?: RegistrarOptions
|
|
82
|
+
) {
|
|
83
|
+
const { environment = 'production', ...clientOptions } = options ?? {};
|
|
84
|
+
this.credentials = credentials;
|
|
85
|
+
this.environment = environment;
|
|
86
|
+
this.options = { ...DEFAULT_OPTIONS, ...clientOptions };
|
|
87
|
+
this.http = new HttpClient({ ...httpConfig, options: this.options });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
testConnection(_opts?: RequestOptions): Promise<ConnectionResult> {
|
|
91
|
+
return Promise.reject(
|
|
92
|
+
new NotImplementedError(`${this.name}: testConnection is not implemented`)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
listDomains(_opts?: ListDomainsOptions): Promise<Domain[]> {
|
|
97
|
+
return Promise.reject(new NotImplementedError(`${this.name}: listDomains is not implemented`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
renewDomain(
|
|
101
|
+
_domainName: string,
|
|
102
|
+
_years?: number,
|
|
103
|
+
_opts?: RequestOptions
|
|
104
|
+
): Promise<OperationResult> {
|
|
105
|
+
return Promise.reject(new NotImplementedError(`${this.name}: renewDomain is not implemented`));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
updateNameservers(
|
|
109
|
+
_domainName: string,
|
|
110
|
+
_nameservers: string[],
|
|
111
|
+
_opts?: RequestOptions
|
|
112
|
+
): Promise<OperationResult> {
|
|
113
|
+
return Promise.reject(
|
|
114
|
+
new NotImplementedError(`${this.name}: updateNameservers is not implemented`)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lockDomain(_domainName: string, _opts?: RequestOptions): Promise<OperationResult> {
|
|
119
|
+
return Promise.reject(new NotImplementedError(`${this.name}: lockDomain is not implemented`));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
unlockDomain(_domainName: string, _opts?: RequestOptions): Promise<OperationResult> {
|
|
123
|
+
return Promise.reject(new NotImplementedError(`${this.name}: unlockDomain is not implemented`));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getDomain(_domainName: string, _opts?: RequestOptions): Promise<Domain> {
|
|
127
|
+
return this.notImplemented('getDomain');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
checkAvailability(_domainNames: string[], _opts?: RequestOptions): Promise<DomainAvailability[]> {
|
|
131
|
+
return this.notImplemented('checkAvailability');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
getPricing(_tldOrDomain: string, _opts?: RequestOptions): Promise<TldPricing> {
|
|
135
|
+
return this.notImplemented('getPricing');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
registerDomain(
|
|
139
|
+
_domainName: string,
|
|
140
|
+
_input: RegisterDomainInput,
|
|
141
|
+
_opts?: RequestOptions
|
|
142
|
+
): Promise<OperationResult> {
|
|
143
|
+
return this.notImplemented('registerDomain');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
setAutoRenew(
|
|
147
|
+
_domainName: string,
|
|
148
|
+
_enabled: boolean,
|
|
149
|
+
_opts?: RequestOptions
|
|
150
|
+
): Promise<OperationResult> {
|
|
151
|
+
return this.notImplemented('setAutoRenew');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
transferIn(
|
|
155
|
+
_domainName: string,
|
|
156
|
+
_input: TransferDomainInput,
|
|
157
|
+
_opts?: RequestOptions
|
|
158
|
+
): Promise<OperationResult> {
|
|
159
|
+
return this.notImplemented('transferIn');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getNameservers(_domainName: string, _opts?: RequestOptions): Promise<string[]> {
|
|
163
|
+
return this.notImplemented('getNameservers');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setPrivacy(
|
|
167
|
+
_domainName: string,
|
|
168
|
+
_enabled: boolean,
|
|
169
|
+
_opts?: RequestOptions
|
|
170
|
+
): Promise<OperationResult> {
|
|
171
|
+
return this.notImplemented('setPrivacy');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getContacts(_domainName: string, _opts?: RequestOptions): Promise<ContactSet> {
|
|
175
|
+
return this.notImplemented('getContacts');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
updateContacts(
|
|
179
|
+
_domainName: string,
|
|
180
|
+
_contacts: ContactSet,
|
|
181
|
+
_opts?: RequestOptions
|
|
182
|
+
): Promise<OperationResult> {
|
|
183
|
+
return this.notImplemented('updateContacts');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getDnsRecords(_domainName: string, _opts?: RequestOptions): Promise<DnsRecord[]> {
|
|
187
|
+
return this.notImplemented('getDnsRecords');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setDnsRecords(
|
|
191
|
+
_domainName: string,
|
|
192
|
+
_records: DnsRecord[],
|
|
193
|
+
_opts?: RequestOptions
|
|
194
|
+
): Promise<OperationResult> {
|
|
195
|
+
return this.notImplemented('setDnsRecords');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- extended capabilities (opt-in; default to NotImplementedError) ---
|
|
199
|
+
|
|
200
|
+
getEmailForwarding(_domainName: string, _opts?: RequestOptions): Promise<EmailForward[]> {
|
|
201
|
+
return this.notImplemented('getEmailForwarding');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
setEmailForwarding(
|
|
205
|
+
_domainName: string,
|
|
206
|
+
_forwards: EmailForward[],
|
|
207
|
+
_opts?: RequestOptions
|
|
208
|
+
): Promise<OperationResult> {
|
|
209
|
+
return this.notImplemented('setEmailForwarding');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getDomainForwarding(_domainName: string, _opts?: RequestOptions): Promise<DomainForward[]> {
|
|
213
|
+
return this.notImplemented('getDomainForwarding');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setDomainForwarding(
|
|
217
|
+
_domainName: string,
|
|
218
|
+
_forwards: DomainForward[],
|
|
219
|
+
_opts?: RequestOptions
|
|
220
|
+
): Promise<OperationResult> {
|
|
221
|
+
return this.notImplemented('setDomainForwarding');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
getAuthCode(_domainName: string, _opts?: RequestOptions): Promise<string> {
|
|
225
|
+
return this.notImplemented('getAuthCode');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
getDnssec(_domainName: string, _opts?: RequestOptions): Promise<DnssecStatus> {
|
|
229
|
+
return this.notImplemented('getDnssec');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
disableDnssec(_domainName: string, _opts?: RequestOptions): Promise<OperationResult> {
|
|
233
|
+
return this.notImplemented('disableDnssec');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// shared rejection for a capability a provider hasn't wired up yet
|
|
237
|
+
protected notImplemented(method: string): Promise<never> {
|
|
238
|
+
return Promise.reject(new NotImplementedError(`${this.name}: ${method} is not implemented`));
|
|
239
|
+
}
|
|
240
|
+
}
|