@aoxborrow/registrar-client 0.10.1 → 0.12.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 +26 -0
- package/dist/index.cjs +276 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +276 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/features.ts +9 -0
- package/src/registrar.ts +13 -0
- package/src/registrars/dynadot.ts +137 -3
- package/src/registrars/gandi.ts +91 -3
- package/src/registrars/godaddy.ts +136 -8
- package/src/types.ts +38 -0
package/README.md
CHANGED
|
@@ -187,6 +187,9 @@ are the guaranteed contract; **extended** methods are opt-in per provider.
|
|
|
187
187
|
| Extended method | CF | DY | GA | GD | NB | NC | NS | PB | SP | NM | 101 |
|
|
188
188
|
| --------------------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
189
189
|
| `getAuthCode` | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ⁱ | ✗ⁱ | ✗ | ✓ | ✓ | ✗ |
|
|
190
|
+
| `getTransferOut` | ✗ | ✓ⁿ | ✓ⁿ | ✓ⁿ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
|
191
|
+
| `approveTransferOut` | ✗ | ✓ⁿ | ✓ⁿ | ✓ⁿ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
|
192
|
+
| `rejectTransferOut` | ✗ | ✓ⁿ | ✓ⁿ | ✓ⁿ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ⁿ | ✗ |
|
|
190
193
|
| `getDnssec` | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ᵐ | ✗ |
|
|
191
194
|
| `disableDnssec` | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ᵐ | ✗ |
|
|
192
195
|
| `getEmailForwarding` | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ᵐ | ✗ |
|
|
@@ -232,6 +235,16 @@ not a claim of available API endpoints. See [101domain](docs/registrars/101domai
|
|
|
232
235
|
- **ʲ** Dynadot and NameSilo forward the whole domain (a single `@` rule);
|
|
233
236
|
clearing a forward restores the default nameservers.
|
|
234
237
|
- **ᵏ** Gandi web redirects are per host, apex included; creating one replaces the host's DNS record.
|
|
238
|
+
- **ⁿ** Transfer-out approval answers a transfer another registrar requested with
|
|
239
|
+
the auth code. Dynadot's is sandbox-verified only up to "no such order" (its
|
|
240
|
+
sandbox can't start an outbound transfer); a real pending one's read was
|
|
241
|
+
checked in production. GoDaddy's uses the v2 customer-scoped API, so it needs a
|
|
242
|
+
`customerId` as forwarding does (ˡ); it reads the domain's recent actions, with
|
|
243
|
+
no expiry date. Gandi has no transfer-out to read: a `pendingTransfer` status
|
|
244
|
+
reads as pending, with no dates, and approving sends the current auth code
|
|
245
|
+
back. Neither has been tried on a real transfer yet. Name.com can only reject
|
|
246
|
+
(not wired up); the rest leave it to the dashboard or the transfer email, and
|
|
247
|
+
release the domain after about 5 days. See each registrar's doc.
|
|
235
248
|
- **ˡ** GoDaddy domain forwarding uses the v2 customer-scoped API, so it requires
|
|
236
249
|
a `customerId` (the customer UUID, or a numeric shopper ID the client resolves to
|
|
237
250
|
the UUID via an sso-key-only lookup); the old v1 forwarding route was removed.
|
|
@@ -335,6 +348,19 @@ the registrant); Namecheap requires all four roles; Dynadot and Porkbun use the
|
|
|
335
348
|
account's default WHOIS contact. Transfers carry over the existing contacts, so
|
|
336
349
|
most providers need only the auth code (`TransferDomainInput`).
|
|
337
350
|
|
|
351
|
+
Going the other way, a sold or departing name leaves with its auth code
|
|
352
|
+
(`getAuthCode`). When the buyer's registrar requests it, the losing registrar
|
|
353
|
+
holds the transfer for a few days unless the owner answers it; where the API
|
|
354
|
+
allows, `getTransferOut` reads that request and `approveTransferOut` /
|
|
355
|
+
`rejectTransferOut` answer it:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
if (provider.supports(Feature.GetTransferOut)) {
|
|
359
|
+
const transfer = await provider.getTransferOut('example.com'); // null if none
|
|
360
|
+
if (transfer?.status === 'pending') await provider.approveTransferOut('example.com');
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
338
364
|
> `registerDomain` is implemented for **every bundled provider**, and `transferIn`
|
|
339
365
|
> for every one whose API has a transfer endpoint (all except **Cloudflare** and
|
|
340
366
|
> **NameBright**). `registerDomain` has been exercised live against several
|
package/dist/index.cjs
CHANGED
|
@@ -685,6 +685,12 @@ var Feature = {
|
|
|
685
685
|
// --- extended (opt-in; declared per provider) ---
|
|
686
686
|
GetAuthCode: "getAuthCode",
|
|
687
687
|
// retrieve transfer auth/EPP code (transfer out)
|
|
688
|
+
GetTransferOut: "getTransferOut",
|
|
689
|
+
// read an outbound transfer another registrar requested
|
|
690
|
+
ApproveTransferOut: "approveTransferOut",
|
|
691
|
+
// release a pending outbound transfer now
|
|
692
|
+
RejectTransferOut: "rejectTransferOut",
|
|
693
|
+
// refuse a pending outbound transfer
|
|
688
694
|
GetDnssec: "getDnssec",
|
|
689
695
|
// read whether DNSSEC is enabled (DS / key records)
|
|
690
696
|
DisableDnssec: "disableDnssec",
|
|
@@ -720,6 +726,9 @@ var CORE_FEATURES = [
|
|
|
720
726
|
];
|
|
721
727
|
var EXTENDED_FEATURES = [
|
|
722
728
|
Feature.GetAuthCode,
|
|
729
|
+
Feature.GetTransferOut,
|
|
730
|
+
Feature.ApproveTransferOut,
|
|
731
|
+
Feature.RejectTransferOut,
|
|
723
732
|
Feature.GetDnssec,
|
|
724
733
|
Feature.DisableDnssec,
|
|
725
734
|
Feature.GetEmailForwarding,
|
|
@@ -752,6 +761,9 @@ var FEATURE_CALLS = {
|
|
|
752
761
|
getDnsRecords: { intent: "read", optsIndex: 1 },
|
|
753
762
|
setDnsRecords: { intent: "write", optsIndex: 2 },
|
|
754
763
|
getAuthCode: { intent: "write", optsIndex: 1 },
|
|
764
|
+
getTransferOut: { intent: "read", optsIndex: 1 },
|
|
765
|
+
approveTransferOut: { intent: "write", optsIndex: 1 },
|
|
766
|
+
rejectTransferOut: { intent: "write", optsIndex: 1 },
|
|
755
767
|
getDnssec: { intent: "read", optsIndex: 1 },
|
|
756
768
|
disableDnssec: { intent: "write", optsIndex: 1 },
|
|
757
769
|
getEmailForwarding: { intent: "read", optsIndex: 1 },
|
|
@@ -913,6 +925,15 @@ var BaseRegistrar = class {
|
|
|
913
925
|
getAuthCode(_domainName, _opts) {
|
|
914
926
|
return this.notImplemented("getAuthCode");
|
|
915
927
|
}
|
|
928
|
+
getTransferOut(_domainName, _opts) {
|
|
929
|
+
return this.notImplemented("getTransferOut");
|
|
930
|
+
}
|
|
931
|
+
approveTransferOut(_domainName, _opts) {
|
|
932
|
+
return this.notImplemented("approveTransferOut");
|
|
933
|
+
}
|
|
934
|
+
rejectTransferOut(_domainName, _opts) {
|
|
935
|
+
return this.notImplemented("rejectTransferOut");
|
|
936
|
+
}
|
|
916
937
|
getDnssec(_domainName, _opts) {
|
|
917
938
|
return this.notImplemented("getDnssec");
|
|
918
939
|
}
|
|
@@ -2333,11 +2354,16 @@ var DynadotRegistrar = class extends BaseRegistrar {
|
|
|
2333
2354
|
];
|
|
2334
2355
|
static supportsSandbox = true;
|
|
2335
2356
|
// api-sandbox.dynadot.com mirrors the API
|
|
2336
|
-
// On top of core: transfer-out auth code, DNSSEC read/disable,
|
|
2337
|
-
// domain (URL) forwarding. All implemented on the v2 transport and
|
|
2338
|
-
// the Dynadot sandbox
|
|
2357
|
+
// On top of core: transfer-out auth code and approval, DNSSEC read/disable,
|
|
2358
|
+
// and email + domain (URL) forwarding. All implemented on the v2 transport and
|
|
2359
|
+
// verified in the Dynadot sandbox (transfer-out approval only up to "no such
|
|
2360
|
+
// order": the sandbox can't create an outbound transfer; a real pending
|
|
2361
|
+
// one's read was checked in production).
|
|
2339
2362
|
static extendedFeatures = [
|
|
2340
2363
|
Feature.GetAuthCode,
|
|
2364
|
+
Feature.GetTransferOut,
|
|
2365
|
+
Feature.ApproveTransferOut,
|
|
2366
|
+
Feature.RejectTransferOut,
|
|
2341
2367
|
Feature.GetDnssec,
|
|
2342
2368
|
Feature.DisableDnssec,
|
|
2343
2369
|
Feature.GetEmailForwarding,
|
|
@@ -2808,6 +2834,64 @@ ${bodyStr}`;
|
|
|
2808
2834
|
);
|
|
2809
2835
|
return data.auth_code ?? "";
|
|
2810
2836
|
}
|
|
2837
|
+
/**
|
|
2838
|
+
* The domain's latest outbound transfer via GET .../transfer_status with
|
|
2839
|
+
* `transfer_type=transfer_away`, which lists its transfer-away orders. Dynadot
|
|
2840
|
+
* answers 404 when there are none (and for a domain not in the account), read
|
|
2841
|
+
* here as null. An open order wins over a newer finished one, so approve and
|
|
2842
|
+
* reject act on the order that can still be answered.
|
|
2843
|
+
*/
|
|
2844
|
+
async getTransferOut(domainName, opts) {
|
|
2845
|
+
let env;
|
|
2846
|
+
try {
|
|
2847
|
+
env = await this.signedRequest(
|
|
2848
|
+
"GET",
|
|
2849
|
+
`/restful/v2/domains/${encodeURIComponent(domainName)}/transfer_status?transfer_type=transfer_away`,
|
|
2850
|
+
void 0,
|
|
2851
|
+
opts
|
|
2852
|
+
);
|
|
2853
|
+
} catch (error) {
|
|
2854
|
+
if (error instanceof NotFoundError) return null;
|
|
2855
|
+
throw error;
|
|
2856
|
+
}
|
|
2857
|
+
if (env.code === 404) return null;
|
|
2858
|
+
if (!isOk(env.code)) {
|
|
2859
|
+
throw new Error(env.error?.description ?? env.message ?? "Dynadot request failed");
|
|
2860
|
+
}
|
|
2861
|
+
const transfers = (env.data?.domain_transfer_status_list ?? []).map(toTransferOut).sort((a, b) => (b.requestedDate?.getTime() ?? 0) - (a.requestedDate?.getTime() ?? 0));
|
|
2862
|
+
return transfers.find((t) => OPEN_TRANSFER_OUT.has(t.status)) ?? transfers[0] ?? null;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Approves the pending outbound transfer, releasing the domain now instead of
|
|
2866
|
+
* after Dynadot's 5-day wait. Answers the order `getTransferOut` reports.
|
|
2867
|
+
*/
|
|
2868
|
+
async approveTransferOut(domainName, opts) {
|
|
2869
|
+
return this.authorizeTransferAway(domainName, true, opts);
|
|
2870
|
+
}
|
|
2871
|
+
// Denies the pending outbound transfer; the domain stays in the account.
|
|
2872
|
+
async rejectTransferOut(domainName, opts) {
|
|
2873
|
+
return this.authorizeTransferAway(domainName, false, opts);
|
|
2874
|
+
}
|
|
2875
|
+
// POST /orders/{order_id}/authorize_transfer_away with `approve` true or
|
|
2876
|
+
// false. Dynadot answers 409 when the order is no longer waiting on the owner.
|
|
2877
|
+
async authorizeTransferAway(domainName, approve, opts) {
|
|
2878
|
+
let transfer;
|
|
2879
|
+
try {
|
|
2880
|
+
transfer = await this.getTransferOut(domainName, opts);
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
2883
|
+
}
|
|
2884
|
+
if (!transfer?.id) {
|
|
2885
|
+
return { success: false, message: `No transfer-out of ${domainName} to answer` };
|
|
2886
|
+
}
|
|
2887
|
+
return this.mutate(
|
|
2888
|
+
"POST",
|
|
2889
|
+
`/restful/v2/orders/${encodeURIComponent(transfer.id)}/authorize_transfer_away`,
|
|
2890
|
+
{ domain_name: domainName, approve },
|
|
2891
|
+
`Transfer of ${domainName} ${approve ? "approved" : "rejected"}`,
|
|
2892
|
+
opts
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2811
2895
|
/**
|
|
2812
2896
|
* DNSSEC status via GET .../dnssec, which returns a list of DS records
|
|
2813
2897
|
* (`dnssec_info_list`); a non-empty list means DNSSEC is enabled. `algorithm`
|
|
@@ -2956,6 +3040,32 @@ async function hmacSha256Base64(secret, message) {
|
|
|
2956
3040
|
for (const byte of new Uint8Array(signature)) binary += String.fromCharCode(byte);
|
|
2957
3041
|
return btoa(binary);
|
|
2958
3042
|
}
|
|
3043
|
+
var OPEN_TRANSFER_OUT = /* @__PURE__ */ new Set(["pending", "unknown"]);
|
|
3044
|
+
function toTransferOut(t) {
|
|
3045
|
+
const rawStatus = t.transfer_status ?? "";
|
|
3046
|
+
let status = transferOutStatus(rawStatus);
|
|
3047
|
+
if (status === "unknown" && msDate(t.order_completed_date)) status = "completed";
|
|
3048
|
+
return {
|
|
3049
|
+
status,
|
|
3050
|
+
rawStatus,
|
|
3051
|
+
id: str(t.order_id) || void 0,
|
|
3052
|
+
requestedDate: msDate(t.order_created_date),
|
|
3053
|
+
expirationDate: msDate(t.expiration_date),
|
|
3054
|
+
completedDate: msDate(t.order_completed_date)
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
function transferOutStatus(label) {
|
|
3058
|
+
const s = label.toLowerCase();
|
|
3059
|
+
if (/den(y|ied)|reject|refus|not approved|nack/.test(s)) return "rejected";
|
|
3060
|
+
if (/cancel|fail|expire|time ?out|abort/.test(s)) return "cancelled";
|
|
3061
|
+
if (/complete|transferr?ed|success/.test(s)) return "completed";
|
|
3062
|
+
if (/pend|wait|await|need|request|none|new|process|hold|review/.test(s)) return "pending";
|
|
3063
|
+
if (/approv|authori[sz]|accept/.test(s)) return "approved";
|
|
3064
|
+
return "unknown";
|
|
3065
|
+
}
|
|
3066
|
+
function msDate(ms) {
|
|
3067
|
+
return typeof ms === "number" && ms > 0 ? new Date(ms) : null;
|
|
3068
|
+
}
|
|
2959
3069
|
function isOk(code) {
|
|
2960
3070
|
return code != null && code >= 200 && code < 300;
|
|
2961
3071
|
}
|
|
@@ -3102,9 +3212,9 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
3102
3212
|
];
|
|
3103
3213
|
// Gandi's v5 API offers a sandbox at api.sandbox.gandi.net (separate account)
|
|
3104
3214
|
static supportsSandbox = true;
|
|
3105
|
-
// On top of core: transfer-out auth code (`authinfo`), DNSSEC
|
|
3106
|
-
// (LiveDNS keys), email forwarding, and web/URL forwarding
|
|
3107
|
-
// per host, apex included). Note core `setPrivacy` disabling is a no-op for
|
|
3215
|
+
// On top of core: transfer-out auth code (`authinfo`) and approval, DNSSEC
|
|
3216
|
+
// read/disable (LiveDNS keys), email forwarding, and web/URL forwarding
|
|
3217
|
+
// (webredirs; per host, apex included). Note core `setPrivacy` disabling is a no-op for
|
|
3108
3218
|
// individual (natural-person) registrants — Gandi keeps WHOIS obfuscation on
|
|
3109
3219
|
// for GDPR regardless of the request.
|
|
3110
3220
|
static extendedFeatures = [
|
|
@@ -3114,7 +3224,10 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
3114
3224
|
Feature.GetEmailForwarding,
|
|
3115
3225
|
Feature.SetEmailForwarding,
|
|
3116
3226
|
Feature.GetDomainForwarding,
|
|
3117
|
-
Feature.SetDomainForwarding
|
|
3227
|
+
Feature.SetDomainForwarding,
|
|
3228
|
+
Feature.GetTransferOut,
|
|
3229
|
+
Feature.ApproveTransferOut,
|
|
3230
|
+
Feature.RejectTransferOut
|
|
3118
3231
|
];
|
|
3119
3232
|
constructor(credentials, options) {
|
|
3120
3233
|
super(
|
|
@@ -3366,6 +3479,58 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
3366
3479
|
});
|
|
3367
3480
|
return d.authinfo ?? "";
|
|
3368
3481
|
}
|
|
3482
|
+
/**
|
|
3483
|
+
* The domain's pending outbound transfer. Gandi has no transfer-out resource
|
|
3484
|
+
* to read: a domain in the account carries the documented `pendingTransfer`
|
|
3485
|
+
* EPP status while another registrar's request waits (inbound transfers live
|
|
3486
|
+
* under /domain/transferin, so on a domain you hold it's outbound — inferred,
|
|
3487
|
+
* not documented). Gandi reports no dates for it, and nothing once it's
|
|
3488
|
+
* answered or done, so this reads `pending` or null.
|
|
3489
|
+
*/
|
|
3490
|
+
async getTransferOut(domainName, opts) {
|
|
3491
|
+
const d = await this.http.request({
|
|
3492
|
+
path: `/domain/domains/${domainName}`,
|
|
3493
|
+
...opts
|
|
3494
|
+
});
|
|
3495
|
+
return pendingTransferOut(d);
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Approves the pending outbound transfer, releasing the domain now instead of
|
|
3499
|
+
* after Gandi's five days: POST .../transferout with `accept: true` and the
|
|
3500
|
+
* domain's current auth code, which Gandi requires back (→ 202).
|
|
3501
|
+
*/
|
|
3502
|
+
async approveTransferOut(domainName, opts) {
|
|
3503
|
+
return this.answerTransferOut(domainName, true, opts);
|
|
3504
|
+
}
|
|
3505
|
+
// Declines the pending outbound transfer (`accept: false`, no reason); the
|
|
3506
|
+
// domain stays in the account.
|
|
3507
|
+
async rejectTransferOut(domainName, opts) {
|
|
3508
|
+
return this.answerTransferOut(domainName, false, opts);
|
|
3509
|
+
}
|
|
3510
|
+
// One read for both the pending status and the auth code the answer needs.
|
|
3511
|
+
async answerTransferOut(domainName, accept, opts) {
|
|
3512
|
+
let d;
|
|
3513
|
+
try {
|
|
3514
|
+
d = await this.http.request({
|
|
3515
|
+
path: `/domain/domains/${domainName}`,
|
|
3516
|
+
...opts
|
|
3517
|
+
});
|
|
3518
|
+
} catch (error) {
|
|
3519
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
3520
|
+
}
|
|
3521
|
+
if (!pendingTransferOut(d)) {
|
|
3522
|
+
return { success: false, message: `No transfer-out of ${domainName} to answer` };
|
|
3523
|
+
}
|
|
3524
|
+
return this.mutate(
|
|
3525
|
+
{
|
|
3526
|
+
method: "POST",
|
|
3527
|
+
path: `/domain/domains/${domainName}/transferout`,
|
|
3528
|
+
body: { accept, authinfo: d.authinfo ?? "" }
|
|
3529
|
+
},
|
|
3530
|
+
`Transfer of ${domainName} ${accept ? "approved" : "rejected"}`,
|
|
3531
|
+
opts
|
|
3532
|
+
);
|
|
3533
|
+
}
|
|
3369
3534
|
/**
|
|
3370
3535
|
* DNSSEC status via Gandi LiveDNS keys (GET /livedns/domains/{fqdn}/keys). A
|
|
3371
3536
|
* non-deleted key means DNSSEC is enabled; each key's `ds` field is the full DS
|
|
@@ -3733,6 +3898,17 @@ var GandiRegistrar = class extends BaseRegistrar {
|
|
|
3733
3898
|
}
|
|
3734
3899
|
}
|
|
3735
3900
|
};
|
|
3901
|
+
function pendingTransferOut(d) {
|
|
3902
|
+
const statuses = Array.isArray(d.status) ? d.status : d.status ? [d.status] : [];
|
|
3903
|
+
if (!statuses.includes("pendingTransfer")) return null;
|
|
3904
|
+
return {
|
|
3905
|
+
status: "pending",
|
|
3906
|
+
rawStatus: "pendingTransfer",
|
|
3907
|
+
requestedDate: null,
|
|
3908
|
+
expirationDate: null,
|
|
3909
|
+
completedDate: null
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3736
3912
|
function toGandiContact(c) {
|
|
3737
3913
|
const contact = {
|
|
3738
3914
|
given: c.firstName,
|
|
@@ -3873,17 +4049,22 @@ var GoDaddyRegistrar = class _GoDaddyRegistrar extends BaseRegistrar {
|
|
|
3873
4049
|
];
|
|
3874
4050
|
// GoDaddy's OTE ("Operational Test Environment") is its sandbox
|
|
3875
4051
|
static supportsSandbox = true;
|
|
3876
|
-
// Beyond core: domain forwarding
|
|
3877
|
-
// field on the v1 domain-detail response)
|
|
3878
|
-
// customer-scoped route
|
|
3879
|
-
//
|
|
3880
|
-
//
|
|
4052
|
+
// Beyond core: domain forwarding, transfer-out auth code (the `authCode`
|
|
4053
|
+
// field on the v1 domain-detail response) and transfer-out approval.
|
|
4054
|
+
// Forwarding uses the v2 customer-scoped route
|
|
4055
|
+
// /v2/customers/{customerId}/domains/forwards/{fqdn} (the old v1
|
|
4056
|
+
// /v1/domains/forwards routes now 404); it requires `customerId` to be
|
|
4057
|
+
// configured — verified live 2026-08-29. Transfer-out approval is v2
|
|
4058
|
+
// customer-scoped too, so it needs `customerId` as well. DNSSEC has no dedicated
|
|
3881
4059
|
// endpoint (DS records go through the generic DNS API), and there's no
|
|
3882
4060
|
// glue-record, email, or push-webhook API.
|
|
3883
4061
|
static extendedFeatures = [
|
|
3884
4062
|
Feature.GetAuthCode,
|
|
3885
4063
|
Feature.GetDomainForwarding,
|
|
3886
|
-
Feature.SetDomainForwarding
|
|
4064
|
+
Feature.SetDomainForwarding,
|
|
4065
|
+
Feature.GetTransferOut,
|
|
4066
|
+
Feature.ApproveTransferOut,
|
|
4067
|
+
Feature.RejectTransferOut
|
|
3887
4068
|
];
|
|
3888
4069
|
// true → prefer the v3 API (production + PAT). false → all calls use v1
|
|
3889
4070
|
// (sso-key auth, or OTE/sandbox where v3 does not exist).
|
|
@@ -4484,7 +4665,7 @@ var GoDaddyRegistrar = class _GoDaddyRegistrar extends BaseRegistrar {
|
|
|
4484
4665
|
const configured = this.credentials.customerId?.trim();
|
|
4485
4666
|
if (!configured) {
|
|
4486
4667
|
throw new ConfigurationError(
|
|
4487
|
-
`${this.name}: domain forwarding
|
|
4668
|
+
`${this.name}: domain forwarding and transfer-out approval use the v2 customer-scoped API, which requires a customer ID. Set \`customerId\` to your GoDaddy customer UUID (or your numeric shopper/account ID, which will be resolved to the UUID).`
|
|
4488
4669
|
);
|
|
4489
4670
|
}
|
|
4490
4671
|
if (UUID_RE.test(configured)) return configured;
|
|
@@ -4584,6 +4765,52 @@ var GoDaddyRegistrar = class _GoDaddyRegistrar extends BaseRegistrar {
|
|
|
4584
4765
|
forwardPath(customerId, fqdn) {
|
|
4585
4766
|
return `/v2/customers/${encodeURIComponent(customerId)}/domains/forwards/${encodeURIComponent(fqdn)}`;
|
|
4586
4767
|
}
|
|
4768
|
+
// the v2 customer-scoped path for a domain
|
|
4769
|
+
domainPathV2(customerId, domainName) {
|
|
4770
|
+
return `/v2/customers/${encodeURIComponent(customerId)}/domains/${encodeURIComponent(domainName)}`;
|
|
4771
|
+
}
|
|
4772
|
+
/**
|
|
4773
|
+
* The domain's latest outbound transfer, read from its recent actions
|
|
4774
|
+
* (GET /v2/customers/{customerId}/domains/{domain}/actions): the TRANSFER_OUT
|
|
4775
|
+
* action, and an accept or reject logged after it. Null when there is no
|
|
4776
|
+
* TRANSFER_OUT on record. GoDaddy reports no expiry; left alone, it releases
|
|
4777
|
+
* the domain within seven days. Shapes from the published v2 spec; a real
|
|
4778
|
+
* transfer-out is not yet seen live.
|
|
4779
|
+
*/
|
|
4780
|
+
async getTransferOut(domainName, opts) {
|
|
4781
|
+
const customerId = await this.resolveCustomerId(opts);
|
|
4782
|
+
const actions = await this.http.request({
|
|
4783
|
+
path: `${this.domainPathV2(customerId, domainName)}/actions`,
|
|
4784
|
+
...opts
|
|
4785
|
+
});
|
|
4786
|
+
return toTransferOut2(Array.isArray(actions) ? actions : []);
|
|
4787
|
+
}
|
|
4788
|
+
/**
|
|
4789
|
+
* Approves the pending outbound transfer, releasing the domain now
|
|
4790
|
+
* (POST .../transferOutAccept → 202). GoDaddy answers 409 when none is
|
|
4791
|
+
* pending.
|
|
4792
|
+
*/
|
|
4793
|
+
async approveTransferOut(domainName, opts) {
|
|
4794
|
+
return this.answerTransferOut(domainName, "transferOutAccept", "approved", opts);
|
|
4795
|
+
}
|
|
4796
|
+
// Rejects the pending outbound transfer (POST .../transferOutReject, with no
|
|
4797
|
+
// reason); the domain stays in the account.
|
|
4798
|
+
async rejectTransferOut(domainName, opts) {
|
|
4799
|
+
return this.answerTransferOut(domainName, "transferOutReject", "rejected", opts);
|
|
4800
|
+
}
|
|
4801
|
+
async answerTransferOut(domainName, action, answer, opts) {
|
|
4802
|
+
let customerId;
|
|
4803
|
+
try {
|
|
4804
|
+
customerId = await this.resolveCustomerId(opts);
|
|
4805
|
+
} catch (error) {
|
|
4806
|
+
return { success: false, message: toRegistrarError(error).message };
|
|
4807
|
+
}
|
|
4808
|
+
return this.mutate(
|
|
4809
|
+
{ method: "POST", path: `${this.domainPathV2(customerId, domainName)}/${action}` },
|
|
4810
|
+
`Transfer of ${domainName} ${answer}`,
|
|
4811
|
+
opts
|
|
4812
|
+
);
|
|
4813
|
+
}
|
|
4587
4814
|
/**
|
|
4588
4815
|
* Transfer authorization (EPP) code. GoDaddy exposes it as the `authCode`
|
|
4589
4816
|
* field on the v1 domain-detail response; transfers were never moved to v3,
|
|
@@ -4640,6 +4867,41 @@ var GoDaddyRegistrar = class _GoDaddyRegistrar extends BaseRegistrar {
|
|
|
4640
4867
|
}
|
|
4641
4868
|
}
|
|
4642
4869
|
};
|
|
4870
|
+
function toTransferOut2(actions) {
|
|
4871
|
+
const time = (a) => parseDate(a.createdAt)?.getTime() ?? 0;
|
|
4872
|
+
const byLatest = (types) => actions.filter((a) => types.includes(a.type ?? "")).sort((a, b) => time(b) - time(a));
|
|
4873
|
+
const transfer = byLatest(["TRANSFER_OUT"])[0];
|
|
4874
|
+
if (!transfer) return null;
|
|
4875
|
+
const answer = byLatest(["TRANSFER_OUT_ACCEPT", "TRANSFER_OUT_REJECT"]).find(
|
|
4876
|
+
(a) => time(a) >= time(transfer) && !/^(FAILED|CANCELLED)$/i.test(a.status ?? "")
|
|
4877
|
+
);
|
|
4878
|
+
const rejected = answer?.type === "TRANSFER_OUT_REJECT";
|
|
4879
|
+
const rawStatus = transfer.status ?? "";
|
|
4880
|
+
let status;
|
|
4881
|
+
switch (rawStatus.toUpperCase()) {
|
|
4882
|
+
case "ACCEPTED":
|
|
4883
|
+
case "AWAITING":
|
|
4884
|
+
case "PENDING":
|
|
4885
|
+
status = !answer ? "pending" : rejected ? "rejected" : "approved";
|
|
4886
|
+
break;
|
|
4887
|
+
case "SUCCESS":
|
|
4888
|
+
status = "completed";
|
|
4889
|
+
break;
|
|
4890
|
+
case "FAILED":
|
|
4891
|
+
case "CANCELLED":
|
|
4892
|
+
status = rejected ? "rejected" : "cancelled";
|
|
4893
|
+
break;
|
|
4894
|
+
default:
|
|
4895
|
+
status = "unknown";
|
|
4896
|
+
}
|
|
4897
|
+
return {
|
|
4898
|
+
status,
|
|
4899
|
+
rawStatus,
|
|
4900
|
+
requestedDate: parseDate(transfer.createdAt),
|
|
4901
|
+
expirationDate: null,
|
|
4902
|
+
completedDate: status === "completed" ? parseDate(transfer.completedAt) : null
|
|
4903
|
+
};
|
|
4904
|
+
}
|
|
4643
4905
|
function fromGoDaddyContact(c) {
|
|
4644
4906
|
if (!c) return void 0;
|
|
4645
4907
|
const a = c.addressMailing ?? {};
|