@aoxborrow/registrar-client 0.9.1 → 0.9.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aoxborrow/registrar-client",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "One consistent, fully-typed TypeScript interface to many domain registrar APIs (GoDaddy, Cloudflare, Namecheap, Spaceship, Dynadot, Porkbun, and more) behind a single pluggable multi-provider client",
5
5
  "author": {
6
6
  "name": "Aaron Oxborrow",
@@ -40,7 +40,6 @@ interface NbOrder {
40
40
  // nameservers (those come from account/domains/{domain}/nameservers).
41
41
  interface NbDomain {
42
42
  DomainName?: string;
43
- domain?: string;
44
43
  Status?: string;
45
44
  ExpirationDate?: string;
46
45
  RegistrationDate?: string;
@@ -48,6 +47,8 @@ interface NbDomain {
48
47
  Locked?: boolean;
49
48
  WhoIsPrivacy?: boolean;
50
49
  AuthCode?: string;
50
+ Category?: string;
51
+ UpgradedDomain?: boolean;
51
52
  }
52
53
 
53
54
  // the paged wrapper GET account/domains returns
@@ -55,16 +56,13 @@ interface NbDomainsPage {
55
56
  ResultsTotal?: number;
56
57
  CurrentPage?: number;
57
58
  Domains?: NbDomain[];
58
- domains?: NbDomain[];
59
59
  }
60
60
 
61
61
  // GET account/domains/{domain}/nameservers → { DomainName, NameServers: [...] }.
62
- // Be lenient about casing and a possible bare array.
62
+ // Be lenient about a possible bare array.
63
63
  interface NbNameservers {
64
64
  DomainName?: string;
65
65
  NameServers?: string[];
66
- Nameservers?: string[];
67
- nameservers?: string[];
68
66
  }
69
67
 
70
68
  // a contact object inside GET account/domains/{domain}/contacts/all. NameBright
@@ -160,6 +158,44 @@ interface NbAvailability {
160
158
  };
161
159
  }
162
160
 
161
+ // Every field name the NameBright interfaces above read, keyed by its lowercase
162
+ // form. NameBright's REST API documents (and used to return) PascalCase JSON —
163
+ // `DomainName`, `ExpirationDate`, `IPV4Address` — but now returns camelCase —
164
+ // `domainName`, `expirationDate`, `ipV4Address` (seen live 2026-10-09). The
165
+ // renamed keys aren't a simple first-letter change (`IPV4Address` vs
166
+ // `ipV4Address`, `CNAMERecords` vs `cnameRecords`), so responses are matched to
167
+ // these names case-insensitively instead.
168
+ // prettier-ignore
169
+ const NB_KEYS = new Map(
170
+ [
171
+ // domains
172
+ 'DomainName', 'Status', 'ExpirationDate', 'RegistrationDate', 'AutoRenew', 'Locked',
173
+ 'WhoIsPrivacy', 'AuthCode', 'Category', 'UpgradedDomain', 'ResultsTotal', 'CurrentPage',
174
+ 'Domains', 'NameServers',
175
+ // contacts
176
+ 'RegistrantContact', 'AdministrativeContact', 'TechnicalContact', 'FirstName', 'LastName',
177
+ 'Organization', 'Department', 'Email', 'Address1', 'Address2', 'City', 'Region', 'Country',
178
+ 'PostalCode', 'PhoneCountry', 'Phone', 'FaxCountry', 'Fax',
179
+ // host records
180
+ 'ARecords', 'AAAARecords', 'CNAMERecords', 'MXRecords', 'TXTRecords', 'SRVRecords',
181
+ 'RecordId', 'Subdomain', 'IPV4Address', 'IPV6Address', 'RedirectDomain', 'MailServer',
182
+ 'Priority', 'TextRecord', 'Service', 'Protocol', 'Weight', 'Port', 'Target',
183
+ // availability + orders
184
+ 'ProductTypeName', 'UnitPrice', 'Promotion', 'PromotionPrice', 'Discount', 'Description',
185
+ 'OrderId', 'TotalPrice',
186
+ ].map(k => [k.toLowerCase(), k])
187
+ );
188
+
189
+ // rename a response's keys (recursively) to the PascalCase names in NB_KEYS,
190
+ // whatever casing NameBright sent. Unknown keys pass through unchanged.
191
+ function canonicalKeys(value: unknown): unknown {
192
+ if (Array.isArray(value)) return value.map(canonicalKeys);
193
+ if (value === null || typeof value !== 'object') return value;
194
+ return Object.fromEntries(
195
+ Object.entries(value).map(([k, v]) => [NB_KEYS.get(k.toLowerCase()) ?? k, canonicalKeys(v)])
196
+ );
197
+ }
198
+
163
199
  /**
164
200
  * NameBright Registrar
165
201
  * API docs: https://api.namebright.com/rest/Help
@@ -259,17 +295,19 @@ export class NameBrightRegistrar extends BaseRegistrar {
259
295
  return this.token;
260
296
  }
261
297
 
262
- // issue an authenticated REST request, attaching a fresh bearer token
298
+ // issue an authenticated REST request, attaching a fresh bearer token. The
299
+ // response's keys are normalized to PascalCase (see NB_KEYS).
263
300
  private async authed<T>(
264
301
  config: Omit<RequestConfig, 'headers'>,
265
302
  opts?: RequestOptions
266
303
  ): Promise<T> {
267
304
  const token = await this.getToken(opts);
268
- return this.http.request<T>({
305
+ const res = await this.http.request<unknown>({
269
306
  ...config,
270
307
  headers: { Authorization: `Bearer ${token}` },
271
308
  ...opts,
272
309
  });
310
+ return canonicalKeys(res) as T;
273
311
  }
274
312
 
275
313
  override async testConnection(opts?: RequestOptions): Promise<ConnectionResult> {
@@ -297,9 +335,10 @@ export class NameBrightRegistrar extends BaseRegistrar {
297
335
  reqOpts
298
336
  );
299
337
  // the response may be a bare array or a paged object; be lenient
300
- const list = Array.isArray(data) ? data : (data?.Domains ?? data?.domains ?? []);
338
+ const list = Array.isArray(data) ? data : (data?.Domains ?? []);
301
339
 
302
- for (const d of list) domains.push(this.toDomain(d));
340
+ // skip any row without a name rather than emit a blank domain
341
+ for (const d of list) if (d.DomainName) domains.push(this.toDomain(d));
303
342
 
304
343
  hasMore = list.length === perPage;
305
344
  page++;
@@ -340,7 +379,7 @@ export class NameBrightRegistrar extends BaseRegistrar {
340
379
  opts
341
380
  );
342
381
  if (Array.isArray(res)) return res.map(String);
343
- return (res.NameServers ?? res.Nameservers ?? res.nameservers ?? []).map(String);
382
+ return (res.NameServers ?? []).map(String);
344
383
  }
345
384
 
346
385
  /**
@@ -523,10 +562,12 @@ export class NameBrightRegistrar extends BaseRegistrar {
523
562
  ): Promise<OperationResult> {
524
563
  try {
525
564
  const path = `account/domains/${encodeURIComponent(domainName)}`;
526
- const current = await this.authed<NbDomain & { Category?: string; UpgradedDomain?: boolean }>(
527
- { path },
528
- opts
529
- );
565
+ const current = await this.authed<NbDomain>({ path }, opts);
566
+ // an unreadable record would make the full-object PUT below reset the
567
+ // other flags to false, so refuse instead
568
+ if (!current.DomainName) {
569
+ return { success: false, message: `NameBright: could not read ${domainName} to update it` };
570
+ }
530
571
  const body = {
531
572
  DomainName: current.DomainName,
532
573
  Status: current.Status,
@@ -618,7 +659,7 @@ export class NameBrightRegistrar extends BaseRegistrar {
618
659
  // normalized Domain shape. Neither endpoint returns nameservers.
619
660
  private toDomain(d: NbDomain): Domain {
620
661
  return createDomain({
621
- domainName: d.DomainName ?? d.domain,
662
+ domainName: d.DomainName,
622
663
  registrar: this.name,
623
664
  status: d.Status ?? 'ok',
624
665
  createdDate: d.RegistrationDate,