@aoxborrow/registrar-client 0.9.2 → 0.10.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/dist/index.d.cts CHANGED
@@ -178,10 +178,12 @@ interface EmailForward {
178
178
  forwardTo: string;
179
179
  }
180
180
  type DomainForwardType = 'temporary' | 'permanent' | 'masked';
181
+ type DomainForwardHttps = 'off' | 'on' | 'only';
181
182
  interface DomainForward {
182
183
  host: string;
183
184
  url: string;
184
185
  type: DomainForwardType;
186
+ https?: DomainForwardHttps;
185
187
  }
186
188
  interface DsRecord {
187
189
  keyTag: number;
@@ -575,6 +577,7 @@ declare class GandiRegistrar extends BaseRegistrar {
575
577
  setEmailForwarding(domainName: string, forwards: EmailForward[], opts?: RequestOptions): Promise<OperationResult>;
576
578
  getDomainForwarding(domainName: string, opts?: RequestOptions): Promise<DomainForward[]>;
577
579
  setDomainForwarding(domainName: string, forwards: DomainForward[], opts?: RequestOptions): Promise<OperationResult>;
580
+ private listWebredirs;
578
581
  setAutoRenew(domainName: string, enabled: boolean, opts?: RequestOptions): Promise<OperationResult>;
579
582
  setPrivacy(domainName: string, enabled: boolean, opts?: RequestOptions): Promise<OperationResult>;
580
583
  setDnsRecords(domainName: string, records: DnsRecord[], opts?: RequestOptions): Promise<OperationResult>;
@@ -916,7 +919,8 @@ type SettableDomainForward = DomainForward & {
916
919
  type: 'temporary' | 'permanent';
917
920
  };
918
921
  declare const MASKED_FORWARD_UNSUPPORTED = "masked/framed forwarding is not supported by this library; use \"temporary\" or \"permanent\"";
919
- declare function settableForwards(forwards: DomainForward[]): SettableDomainForward[];
922
+ declare function settableForwards(forwards: DomainForward[], httpsModes?: readonly DomainForwardHttps[]): SettableDomainForward[];
923
+ declare function unsupportedForwardHttps(mode: DomainForwardHttps, httpsModes?: readonly DomainForwardHttps[]): string;
920
924
  declare function requireConsent(provider: string, consent: RegistrationConsent | undefined): RegistrationConsent;
921
925
  declare function filterDomains(domains: Domain[], search?: string): Domain[];
922
926
  declare function normalizeDomain(domain: string): string;
@@ -926,4 +930,5 @@ declare function normalizeNameservers(nameservers: unknown): string[];
926
930
  declare function createDomain(data?: DomainInput): Domain;
927
931
  declare function asRead<T extends RequestOptions>(opts?: T): T;
928
932
 
929
- export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, type ConfigField, ConfigurationError, ConnectionError, type ConnectionResult, ConsentRequiredError, type Contact, type ContactSet, DEFAULT_OPTIONS, type DelegationSigner, type DnsEntry, type DnsRecord, DnsSyntaxError, type DnssecStatus, type Domain, Domain101Registrar, type DomainAvailability, type DomainForward, type DomainForwardType, type DomainInput, type DomainSource, type DsRecord, DynadotRegistrar, EXTENDED_FEATURES, type EmailForward, FEATURE_CALLS, Feature, type FeatureCall, GandiRegistrar, GoDaddyRegistrar, HttpClient, type HttpClientConfig, InvalidResponseError, type LabelMapper, type ListDomainsOptions, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, type NameRole, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, type OperationResult, OutcomeUnknownError, ParsingError, PorkbunRegistrar, type PortfolioError, type PortfolioResult, REST_SAFE_METHODS, RateLimitError, type RecordClash, type RegisterDomainInput, type Registrar, RegistrarClient, type RegistrarClientOptions, type RegistrarConstructor, type RegistrarCredentials, type RegistrarEnvironment, RegistrarError, type RegistrarFeature, type RegistrarName, type RegistrarOptions, type RegistrationConsent, type RequestConfig, type RequestIntent, type RequestOptions, RetryableRegistrarError, type SettableDomainForward, SpaceshipRegistrar, TimeoutError, type TldPricing, type TransferDomainInput, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, useLabelMapper, wasNotSent };
933
+ export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DnsSyntaxError, Domain101Registrar, DynadotRegistrar, EXTENDED_FEATURES, FEATURE_CALLS, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, OutcomeUnknownError, ParsingError, PorkbunRegistrar, REST_SAFE_METHODS, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, unsupportedForwardHttps, useLabelMapper, wasNotSent };
934
+ export type { ConfigField, ConnectionResult, Contact, ContactSet, DelegationSigner, DnsEntry, DnsRecord, DnssecStatus, Domain, DomainAvailability, DomainForward, DomainForwardHttps, DomainForwardType, DomainInput, DomainSource, DsRecord, EmailForward, FeatureCall, HttpClientConfig, LabelMapper, ListDomainsOptions, NameRole, OperationResult, PortfolioError, PortfolioResult, RecordClash, RegisterDomainInput, Registrar, RegistrarClientOptions, RegistrarConstructor, RegistrarCredentials, RegistrarEnvironment, RegistrarFeature, RegistrarName, RegistrarOptions, RegistrationConsent, RequestConfig, RequestIntent, RequestOptions, SettableDomainForward, TldPricing, TransferDomainInput };
package/dist/index.d.ts CHANGED
@@ -178,10 +178,12 @@ interface EmailForward {
178
178
  forwardTo: string;
179
179
  }
180
180
  type DomainForwardType = 'temporary' | 'permanent' | 'masked';
181
+ type DomainForwardHttps = 'off' | 'on' | 'only';
181
182
  interface DomainForward {
182
183
  host: string;
183
184
  url: string;
184
185
  type: DomainForwardType;
186
+ https?: DomainForwardHttps;
185
187
  }
186
188
  interface DsRecord {
187
189
  keyTag: number;
@@ -575,6 +577,7 @@ declare class GandiRegistrar extends BaseRegistrar {
575
577
  setEmailForwarding(domainName: string, forwards: EmailForward[], opts?: RequestOptions): Promise<OperationResult>;
576
578
  getDomainForwarding(domainName: string, opts?: RequestOptions): Promise<DomainForward[]>;
577
579
  setDomainForwarding(domainName: string, forwards: DomainForward[], opts?: RequestOptions): Promise<OperationResult>;
580
+ private listWebredirs;
578
581
  setAutoRenew(domainName: string, enabled: boolean, opts?: RequestOptions): Promise<OperationResult>;
579
582
  setPrivacy(domainName: string, enabled: boolean, opts?: RequestOptions): Promise<OperationResult>;
580
583
  setDnsRecords(domainName: string, records: DnsRecord[], opts?: RequestOptions): Promise<OperationResult>;
@@ -916,7 +919,8 @@ type SettableDomainForward = DomainForward & {
916
919
  type: 'temporary' | 'permanent';
917
920
  };
918
921
  declare const MASKED_FORWARD_UNSUPPORTED = "masked/framed forwarding is not supported by this library; use \"temporary\" or \"permanent\"";
919
- declare function settableForwards(forwards: DomainForward[]): SettableDomainForward[];
922
+ declare function settableForwards(forwards: DomainForward[], httpsModes?: readonly DomainForwardHttps[]): SettableDomainForward[];
923
+ declare function unsupportedForwardHttps(mode: DomainForwardHttps, httpsModes?: readonly DomainForwardHttps[]): string;
920
924
  declare function requireConsent(provider: string, consent: RegistrationConsent | undefined): RegistrationConsent;
921
925
  declare function filterDomains(domains: Domain[], search?: string): Domain[];
922
926
  declare function normalizeDomain(domain: string): string;
@@ -926,4 +930,5 @@ declare function normalizeNameservers(nameservers: unknown): string[];
926
930
  declare function createDomain(data?: DomainInput): Domain;
927
931
  declare function asRead<T extends RequestOptions>(opts?: T): T;
928
932
 
929
- export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, type ConfigField, ConfigurationError, ConnectionError, type ConnectionResult, ConsentRequiredError, type Contact, type ContactSet, DEFAULT_OPTIONS, type DelegationSigner, type DnsEntry, type DnsRecord, DnsSyntaxError, type DnssecStatus, type Domain, Domain101Registrar, type DomainAvailability, type DomainForward, type DomainForwardType, type DomainInput, type DomainSource, type DsRecord, DynadotRegistrar, EXTENDED_FEATURES, type EmailForward, FEATURE_CALLS, Feature, type FeatureCall, GandiRegistrar, GoDaddyRegistrar, HttpClient, type HttpClientConfig, InvalidResponseError, type LabelMapper, type ListDomainsOptions, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, type NameRole, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, type OperationResult, OutcomeUnknownError, ParsingError, PorkbunRegistrar, type PortfolioError, type PortfolioResult, REST_SAFE_METHODS, RateLimitError, type RecordClash, type RegisterDomainInput, type Registrar, RegistrarClient, type RegistrarClientOptions, type RegistrarConstructor, type RegistrarCredentials, type RegistrarEnvironment, RegistrarError, type RegistrarFeature, type RegistrarName, type RegistrarOptions, type RegistrationConsent, type RequestConfig, type RequestIntent, type RequestOptions, RetryableRegistrarError, type SettableDomainForward, SpaceshipRegistrar, TimeoutError, type TldPricing, type TransferDomainInput, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, useLabelMapper, wasNotSent };
933
+ export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DnsSyntaxError, Domain101Registrar, DynadotRegistrar, EXTENDED_FEATURES, FEATURE_CALLS, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, OutcomeUnknownError, ParsingError, PorkbunRegistrar, REST_SAFE_METHODS, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, unsupportedForwardHttps, useLabelMapper, wasNotSent };
934
+ export type { ConfigField, ConnectionResult, Contact, ContactSet, DelegationSigner, DnsEntry, DnsRecord, DnssecStatus, Domain, DomainAvailability, DomainForward, DomainForwardHttps, DomainForwardType, DomainInput, DomainSource, DsRecord, EmailForward, FeatureCall, HttpClientConfig, LabelMapper, ListDomainsOptions, NameRole, OperationResult, PortfolioError, PortfolioResult, RecordClash, RegisterDomainInput, Registrar, RegistrarClientOptions, RegistrarConstructor, RegistrarCredentials, RegistrarEnvironment, RegistrarFeature, RegistrarName, RegistrarOptions, RegistrationConsent, RequestConfig, RequestIntent, RequestOptions, SettableDomainForward, TldPricing, TransferDomainInput };
package/dist/index.js CHANGED
@@ -202,12 +202,18 @@ function sleep(ms) {
202
202
  return new Promise((resolve) => setTimeout(resolve, ms));
203
203
  }
204
204
  var MASKED_FORWARD_UNSUPPORTED = 'masked/framed forwarding is not supported by this library; use "temporary" or "permanent"';
205
- function settableForwards(forwards) {
205
+ function settableForwards(forwards, httpsModes = []) {
206
206
  for (const f of forwards) {
207
207
  if (f.type === "masked") throw new Error(MASKED_FORWARD_UNSUPPORTED);
208
+ if (f.https !== void 0 && !httpsModes.includes(f.https)) {
209
+ throw new Error(unsupportedForwardHttps(f.https, httpsModes));
210
+ }
208
211
  }
209
212
  return forwards;
210
213
  }
214
+ function unsupportedForwardHttps(mode, httpsModes = []) {
215
+ return `forwarding https "${mode}" can't be set on this provider` + (httpsModes.length ? ` (supported: ${httpsModes.map((m) => `"${m}"`).join(", ")})` : "; leave `https` unset to use its default");
216
+ }
211
217
  function requireConsent(provider, consent) {
212
218
  if (!consent) {
213
219
  throw new ConsentRequiredError(
@@ -1268,7 +1274,7 @@ var CloudflareRegistrar = class _CloudflareRegistrar extends BaseRegistrar {
1268
1274
  */
1269
1275
  async setDomainForwarding(domainName, forwards, opts) {
1270
1276
  try {
1271
- const settable = settableForwards(forwards);
1277
+ const settable = settableForwards(forwards, ["on"]);
1272
1278
  const zone = await this.requireZone(domainName, opts);
1273
1279
  const existing = await this.getRedirectRuleset(zone.id, opts);
1274
1280
  if (settable.length === 0) {
@@ -2126,6 +2132,8 @@ var Domain101Registrar = class extends BaseRegistrar {
2126
2132
  throw new ConfigurationError(
2127
2133
  "101domain: only one permanent apex forwarding rule is supported"
2128
2134
  );
2135
+ if (forwards[0]?.https !== void 0)
2136
+ throw new ConfigurationError(`101domain: ${unsupportedForwardHttps(forwards[0].https)}`);
2129
2137
  if (forwards.length) {
2130
2138
  let url;
2131
2139
  try {
@@ -3066,6 +3074,16 @@ var GANDI_TYPE_TO_FORWARD = {
3066
3074
  cloak: "masked"
3067
3075
  // read-only; setDomainForwarding rejects it
3068
3076
  };
3077
+ var GANDI_PROTOCOL = {
3078
+ off: "http",
3079
+ on: "https",
3080
+ only: "httpsonly"
3081
+ };
3082
+ var GANDI_PROTOCOL_TO_HTTPS = {
3083
+ http: "off",
3084
+ https: "on",
3085
+ httpsonly: "only"
3086
+ };
3069
3087
  var GandiRegistrar = class extends BaseRegistrar {
3070
3088
  name = "gandi";
3071
3089
  // a REST API: GET and HEAD never change state
@@ -3080,7 +3098,7 @@ var GandiRegistrar = class extends BaseRegistrar {
3080
3098
  static supportsSandbox = true;
3081
3099
  // On top of core: transfer-out auth code (`authinfo`), DNSSEC read/disable
3082
3100
  // (LiveDNS keys), email forwarding, and web/URL forwarding (webredirs;
3083
- // per-subdomain, no apex). Note core `setPrivacy` disabling is a no-op for
3101
+ // per host, apex included). Note core `setPrivacy` disabling is a no-op for
3084
3102
  // individual (natural-person) registrants — Gandi keeps WHOIS obfuscation on
3085
3103
  // for GDPR regardless of the request.
3086
3104
  static extendedFeatures = [
@@ -3450,73 +3468,128 @@ var GandiRegistrar = class extends BaseRegistrar {
3450
3468
  }
3451
3469
  /**
3452
3470
  * Read web/URL forwarding via GET /domain/domains/{fqdn}/webredirs. Gandi keys
3453
- * each redirect by the full FQDN of the source subdomain; we normalize that back
3454
- * to a host relative to the apex (e.g. `shop.example.com` -> `shop`).
3471
+ * each redirect by the full FQDN of the source host; we normalize that back to
3472
+ * a host relative to the apex (e.g. `shop.example.com` -> `shop`, and the bare
3473
+ * domain -> `@`).
3455
3474
  */
3456
3475
  async getDomainForwarding(domainName, opts) {
3457
- const redirs = await this.http.request({
3458
- path: `/domain/domains/${domainName}/webredirs`,
3459
- ...opts
3460
- });
3461
- return (redirs ?? []).map((r) => ({
3462
- host: gandiFqdnToHost(r.host ?? domainName, domainName),
3463
- url: r.url ?? "",
3464
- type: GANDI_TYPE_TO_FORWARD[(r.type ?? "").toLowerCase()] ?? "permanent"
3465
- }));
3476
+ const redirs = await this.listWebredirs(domainName, opts);
3477
+ return redirs.map((r) => fromGandiWebredir(r, domainName));
3466
3478
  }
3467
3479
  /**
3468
- * Replace web/URL forwarding (full replace; empty clears). Gandi's webredirs
3469
- * endpoint has no update — only create (POST) and delete (DELETE by FQDN) — so
3470
- * this diffs against the current set: delete entries that are gone or changed,
3471
- * then create the desired ones that aren't already present identically.
3480
+ * Replace web/URL forwarding (full replace; empty clears). Diffs against the
3481
+ * current webredirs by host:
3482
+ * - a host that's new is created (POST),
3483
+ * - a host whose url/type/https changed is updated in place (PATCH); with
3484
+ * `https` unset it keeps the rule's existing `protocol`, so an https
3485
+ * forward stays https,
3486
+ * - a host that's no longer wanted is deleted (DELETE),
3487
+ * - an identical rule (apex included) is left untouched.
3488
+ * Creates and updates run before any delete, so a failure part-way never leaves
3489
+ * a host that was meant to keep forwarding without a rule. A failure after some
3490
+ * writes landed is reported as a partial update listing what was applied.
3491
+ *
3492
+ * The apex (`@`) is the bare domain FQDN. Creating a redirect needs Gandi's own
3493
+ * DNS record at the source host, and the apex nearly always has one already
3494
+ * (POST then fails with an opaque 500), so creates send `override: true` to
3495
+ * let Gandi replace the host's record — forwarding the host is the point.
3472
3496
  *
3473
- * Gandi only supports forwarding a subdomain, not the apex; an apex host ("@")
3474
- * throws rather than silently failing with a 500 from the API.
3497
+ * `https` maps to Gandi's `protocol` (off=http, on=https, only=httpsonly).
3498
+ * A new rule with `https` unset omits `protocol`, so Gandi applies its
3499
+ * default (http). Asking for https makes Gandi provision a Let's Encrypt
3500
+ * certificate for the source host; production does this, but the sandbox
3501
+ * can't (it 500s). Gandi repoints the host at its https redirector at once,
3502
+ * and that refuses the host until the certificate exists, so switching a
3503
+ * live rule to https can leave it unreachable for a while (see gandi.md).
3475
3504
  */
3476
3505
  async setDomainForwarding(domainName, forwards, opts) {
3506
+ const applied = [];
3477
3507
  try {
3478
- const settable = settableForwards(forwards);
3508
+ const settable = settableForwards(forwards, ["off", "on", "only"]);
3509
+ const desired = /* @__PURE__ */ new Map();
3479
3510
  for (const f of settable) {
3480
- if (!f.host || f.host === "@") {
3481
- throw new Error(
3482
- `${this.name}: web forwarding requires a subdomain host; Gandi cannot forward the apex ("@")`
3483
- );
3511
+ const host = normalizeForwardHost(f.host);
3512
+ if (desired.has(host)) {
3513
+ throw new Error(`${this.name}: duplicate web forwarding host "${host}"`);
3484
3514
  }
3515
+ desired.set(host, { ...f, host });
3485
3516
  }
3486
- const current = await this.getDomainForwarding(domainName, opts);
3487
- const same = (a, b) => a.host === b.host && a.url === b.url && a.type === b.type;
3488
- for (const existing of current) {
3489
- if (!settable.some((f) => same(f, existing))) {
3490
- await this.http.request({
3491
- method: "DELETE",
3492
- path: `/domain/domains/${domainName}/webredirs/${gandiHostToFqdn(existing.host, domainName)}`,
3493
- ...opts
3517
+ const current = /* @__PURE__ */ new Map();
3518
+ for (const r of await this.listWebredirs(domainName, opts)) {
3519
+ current.set(gandiFqdnToHost(r.host ?? domainName, domainName), r);
3520
+ }
3521
+ const writes = [];
3522
+ const webredirPath = (host) => `/domain/domains/${domainName}/webredirs/${gandiHostToFqdn(host, domainName)}`;
3523
+ for (const [host, f] of desired) {
3524
+ const existing = current.get(host);
3525
+ if (!existing) {
3526
+ writes.push({
3527
+ label: `create ${host}`,
3528
+ run: () => this.http.request({
3529
+ method: "POST",
3530
+ path: `/domain/domains/${domainName}/webredirs`,
3531
+ body: {
3532
+ host: gandiHostToFqdn(host, domainName),
3533
+ type: GANDI_FORWARD_TYPE[f.type],
3534
+ url: f.url,
3535
+ ...f.https ? { protocol: GANDI_PROTOCOL[f.https] } : {},
3536
+ override: true
3537
+ },
3538
+ ...opts
3539
+ })
3494
3540
  });
3541
+ continue;
3495
3542
  }
3496
- }
3497
- for (const f of settable) {
3498
- if (!current.some((c) => same(c, f))) {
3499
- await this.http.request({
3500
- method: "POST",
3501
- path: `/domain/domains/${domainName}/webredirs`,
3502
- // `protocol` is intentionally omitted so Gandi applies its default
3503
- // (http). Requesting `https` makes Gandi provision a Let's Encrypt
3504
- // certificate for the source host, which our normalized model doesn't
3505
- // express (and which the sandbox can't do — it 500s).
3543
+ const now = fromGandiWebredir(existing, domainName);
3544
+ const httpsSame = f.https === void 0 || f.https === now.https;
3545
+ if (now.url === f.url && now.type === f.type && httpsSame) continue;
3546
+ const protocol = f.https ? GANDI_PROTOCOL[f.https] : existing.protocol;
3547
+ writes.push({
3548
+ label: `update ${host}`,
3549
+ run: () => this.http.request({
3550
+ method: "PATCH",
3551
+ path: webredirPath(host),
3506
3552
  body: {
3507
- host: gandiHostToFqdn(f.host, domainName),
3508
3553
  type: GANDI_FORWARD_TYPE[f.type],
3509
- url: f.url
3554
+ url: f.url,
3555
+ ...protocol ? { protocol } : {}
3510
3556
  },
3511
3557
  ...opts
3512
- });
3558
+ })
3559
+ });
3560
+ }
3561
+ for (const host of current.keys()) {
3562
+ if (desired.has(host)) continue;
3563
+ writes.push({
3564
+ label: `delete ${host}`,
3565
+ run: () => this.http.request({ method: "DELETE", path: webredirPath(host), ...opts })
3566
+ });
3567
+ }
3568
+ for (const w of writes) {
3569
+ try {
3570
+ await w.run();
3571
+ } catch (error) {
3572
+ if (applied.length === 0) throw error;
3573
+ const rest = writes.slice(applied.length + 1).map((x) => x.label);
3574
+ return {
3575
+ success: false,
3576
+ message: `Domain forwarding partially updated: ${w.label} failed (${toRegistrarError(error).message}); applied: ${applied.join(", ")}` + (rest.length ? `; not attempted: ${rest.join(", ")}` : "")
3577
+ };
3513
3578
  }
3579
+ applied.push(w.label);
3514
3580
  }
3515
3581
  return { success: true, message: "Domain forwarding updated successfully" };
3516
3582
  } catch (error) {
3517
3583
  return { success: false, message: toRegistrarError(error).message };
3518
3584
  }
3519
3585
  }
3586
+ async listWebredirs(domainName, opts) {
3587
+ const redirs = await this.http.request({
3588
+ path: `/domain/domains/${domainName}/webredirs`,
3589
+ ...opts
3590
+ });
3591
+ return redirs ?? [];
3592
+ }
3520
3593
  async setAutoRenew(domainName, enabled, opts) {
3521
3594
  try {
3522
3595
  await this.http.request({
@@ -3704,6 +3777,18 @@ function gandiFqdnToHost(fqdn, domain) {
3704
3777
  const suffix = `.${domain}`;
3705
3778
  return fqdn.endsWith(suffix) ? fqdn.slice(0, -suffix.length) : fqdn;
3706
3779
  }
3780
+ function normalizeForwardHost(host) {
3781
+ return host ? host : "@";
3782
+ }
3783
+ function fromGandiWebredir(r, domain) {
3784
+ const https = GANDI_PROTOCOL_TO_HTTPS[(r.protocol ?? "").toLowerCase()];
3785
+ return {
3786
+ host: gandiFqdnToHost(r.host ?? domain, domain),
3787
+ url: r.url ?? "",
3788
+ type: GANDI_TYPE_TO_FORWARD[(r.type ?? "").toLowerCase()] ?? "permanent",
3789
+ ...https ? { https } : {}
3790
+ };
3791
+ }
3707
3792
  function fromGandiContact(c) {
3708
3793
  if (!c) return void 0;
3709
3794
  return {
@@ -8416,6 +8501,6 @@ function toDnsEntry(record, defaultTtl = 3600) {
8416
8501
  };
8417
8502
  }
8418
8503
 
8419
- export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DnsSyntaxError, Domain101Registrar, DynadotRegistrar, EXTENDED_FEATURES, FEATURE_CALLS, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, OutcomeUnknownError, ParsingError, PorkbunRegistrar, REST_SAFE_METHODS, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, useLabelMapper, wasNotSent };
8504
+ export { ALL_FEATURES, AbortError, AuthenticationError, AuthorizationError, BaseRegistrar, CORE_FEATURES, CloudflareRegistrar, ConfigurationError, ConnectionError, ConsentRequiredError, DEFAULT_OPTIONS, DnsSyntaxError, Domain101Registrar, DynadotRegistrar, EXTENDED_FEATURES, FEATURE_CALLS, Feature, GandiRegistrar, GoDaddyRegistrar, HttpClient, InvalidResponseError, MASKED_FORWARD_UNSUPPORTED, NameBrightRegistrar, NameSiloRegistrar, NamecheapRegistrar, NamecomRegistrar, NotFoundError, NotImplementedError, OutcomeUnknownError, ParsingError, PorkbunRegistrar, REST_SAFE_METHODS, RateLimitError, RegistrarClient, RegistrarError, RetryableRegistrarError, SpaceshipRegistrar, TimeoutError, USER_AGENT, asRead, backoffDelay, createDomain, createRegistrar, ensureArray, filterDomains, findCnameConflicts, findRepeatedRecords, isCoreFeature, isIpv4, isIpv6, listPortfolio, markNotSent, normalizeDomain, normalizeNameservers, parseDate, parseXml, prepareEntry, prepareName, prepareRdata, readDelegationSigner, registrars, requireConsent, resetLabelMapper, selectBaseUrl, settableForwards, sleep, toDnsEntry, toRegistrarError, unsupportedForwardHttps, useLabelMapper, wasNotSent };
8420
8505
  //# sourceMappingURL=index.js.map
8421
8506
  //# sourceMappingURL=index.js.map