namecheap/ts/domains.ts

577 lines
19 KiB
TypeScript
Raw Normal View History

2025-04-02 15:19:18 +00:00
/**
* Namecheap API Client - Domains Module
*/
import { HttpClient } from './http-client.js';
import type {
IDomainsGetListParams,
IDomainsGetListResponse,
IDomainInfo,
IDomainCheckResponse,
IDomainCheckResult,
IDomainAvailability,
IDomainCreateParams,
IDomainCreateResponse,
IDomainGetInfoResponse,
IDomainInfoResult,
IDomainContacts,
IDomainGetContactsResponse,
IDomainSetContactsParams,
IDomainRenewResponse,
IDomainRenewResult,
IRegistrarLockResponse,
IDomainTldListResponse,
IContactInfo
} from './types.js';
export class Domains {
private client: HttpClient;
/**
* Create a new Domains API handler
* @param client HTTP client instance
*/
constructor(client: HttpClient) {
this.client = client;
}
/**
* Get a list of domains in the Namecheap account
* @param params Optional parameters for filtering and pagination
* @returns Array of domain information objects
*/
async getList(params: IDomainsGetListParams = {}): Promise<{
domains: IDomainInfo[];
paging: {
totalItems: number;
currentPage: number;
pageSize: number;
};
}> {
// Convert parameters to the format expected by the API
const requestParams: Record<string, string | number | boolean> = {};
// Add optional parameters if provided
if (params.Page !== undefined) {
requestParams.Page = params.Page;
}
if (params.PageSize !== undefined) {
requestParams.PageSize = params.PageSize;
}
if (params.SortBy !== undefined) {
requestParams.SortBy = params.SortBy;
}
if (params.ListType !== undefined) {
requestParams.ListType = params.ListType;
}
if (params.SearchTerm !== undefined) {
requestParams.SearchTerm = params.SearchTerm;
}
// Make the API request
const response = await this.client.request<IDomainsGetListResponse>(
'namecheap.domains.getList',
requestParams
);
// Extract domain information from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const domainListResult = commandResponse.DomainGetListResult[0];
const paging = commandResponse.Paging[0];
// Convert the parsed XML structure to a more usable format
const domains = (domainListResult.Domain || []).map(domain => {
const domainInfo: IDomainInfo = {
...domain.$,
// Convert string boolean values to actual booleans
IsExpired: String(domain.$.IsExpired).toLowerCase() === 'true',
IsLocked: String(domain.$.IsLocked).toLowerCase() === 'true',
AutoRenew: String(domain.$.AutoRenew).toLowerCase() === 'true',
IsPremium: String(domain.$.IsPremium).toLowerCase() === 'true',
IsOurDNS: String(domain.$.IsOurDNS).toLowerCase() === 'true',
// Join nameservers array with commas if it exists
Nameservers: Array.isArray(domain.Nameservers) && domain.Nameservers.length > 0 ?
domain.Nameservers[0] : ''
};
return domainInfo;
});
// Convert paging information
const pagingInfo = paging ? {
totalItems: parseInt(paging.TotalItems[0], 10),
currentPage: parseInt(paging.CurrentPage[0], 10),
pageSize: parseInt(paging.PageSize[0], 10)
} : {
totalItems: domains.length,
currentPage: 1,
pageSize: domains.length
};
return {
domains,
paging: pagingInfo
};
}
/**
* Check if domains are available for registration
* @param domainNames Domain name(s) to check (single string or array of strings)
* @returns Domain availability information
*/
async check(domainNames: string | string[]): Promise<IDomainAvailability[]> {
// Convert single domain to array if needed
const domains = Array.isArray(domainNames) ? domainNames : [domainNames];
// Join domains with comma for the API request
const domainList = domains.join(',');
// Make the API request
const response = await this.client.request<IDomainCheckResponse>(
'namecheap.domains.check',
{ DomainList: domainList }
);
// Parse the response to determine availability
const commandResponse = response.ApiResponse.CommandResponse[0];
const results: IDomainCheckResult[] = Array.isArray(commandResponse.DomainCheckResult)
? commandResponse.DomainCheckResult
: [commandResponse.DomainCheckResult];
// Convert the results to a more usable format
return results.map(result => ({
domain: result.$.Domain,
available: String(result.$.Available).toLowerCase() === 'true',
errorNo: parseInt(result.$.ErrorNo || '0', 10),
description: result.$.Description || '',
isPremium: String(result.$.IsPremiumName).toLowerCase() === 'true',
premiumRegistrationPrice: parseFloat(result.$.PremiumRegistrationPrice || '0'),
premiumRenewalPrice: parseFloat(result.$.PremiumRenewalPrice || '0'),
premiumRestorePrice: parseFloat(result.$.PremiumRestorePrice || '0'),
premiumTransferPrice: parseFloat(result.$.PremiumTransferPrice || '0'),
icannFee: parseFloat(result.$.IcannFee || '0'),
eapFee: parseFloat(result.$.EapFee || '0')
}));
}
/**
* Get detailed information about a specific domain
* @param domainName Domain name to get information for
* @param hostName Optional host name for hosted domains
* @returns Detailed domain information
*/
async getInfo(domainName: string, hostName?: string): Promise<IDomainInfoResult> {
const params: Record<string, string> = { DomainName: domainName };
if (hostName) {
params.HostName = hostName;
}
// Make the API request
const response = await this.client.request<IDomainGetInfoResponse>(
'namecheap.domains.getInfo',
params
);
// Extract domain information from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const domainInfo = commandResponse.DomainGetInfoResult[0];
// Convert string boolean values to actual booleans
const result: IDomainInfoResult = {
status: domainInfo.$.Status,
id: parseInt(domainInfo.$.ID, 10),
domainName: domainInfo.$.DomainName,
ownerName: domainInfo.$.OwnerName,
isOwner: String(domainInfo.$.IsOwner).toLowerCase() === 'true',
isPremium: String(domainInfo.$.IsPremium).toLowerCase() === 'true',
createdDate: domainInfo.DomainDetails?.[0]?.CreatedDate?.[0] || '',
expiredDate: domainInfo.DomainDetails?.[0]?.ExpiredDate?.[0] || '',
whoisGuard: {
enabled: domainInfo.Whoisguard?.[0]?.$.Enabled?.toLowerCase() === 'true' || false,
id: domainInfo.Whoisguard?.[0]?.ID?.[0] ? parseInt(domainInfo.Whoisguard[0].ID[0], 10) : 0,
expiredDate: domainInfo.Whoisguard?.[0]?.ExpiredDate?.[0] || ''
},
dnsProvider: domainInfo.DnsDetails?.[0]?.$.ProviderType || '',
modificationRights: {
all: domainInfo.Modificationrights?.[0]?.$.All?.toLowerCase() === 'true' || false
}
};
return result;
}
/**
* Get contact information for a domain
* @param domainName Domain name to get contacts for
* @returns Domain contact information
* @throws Error if the domain name is invalid or the API request fails
*/
async getContacts(domainName: string): Promise<IDomainContacts> {
if (!domainName) {
throw new Error('Domain name is required');
}
try {
// Make the API request
const response = await this.client.request<IDomainGetContactsResponse>(
'namecheap.domains.getContacts',
{ DomainName: domainName }
);
// Check for API errors
if (response.ApiResponse.$.Status !== 'OK') {
const errors = response.ApiResponse.Errors;
if (Array.isArray(errors) && errors.length > 0) {
const error = errors[0] as { Error?: string[] };
if (error.Error && error.Error.length > 0) {
throw new Error(`API Error: ${error.Error.join(', ')}`);
}
}
throw new Error('Failed to get domain contacts');
}
// Extract contact information from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const contacts = commandResponse.DomainContactsResult[0];
// Convert the parsed XML structure to a more usable format
return {
registrant: this.parseContact(contacts.Registrant?.[0]),
tech: this.parseContact(contacts.Tech?.[0]),
admin: this.parseContact(contacts.Admin?.[0]),
auxBilling: this.parseContact(contacts.AuxBilling?.[0])
};
} catch (error) {
if (error instanceof Error) {
throw error;
}
throw new Error(`Failed to get domain contacts: ${String(error)}`);
}
}
/**
* Set contact information for a domain
* @param domainName Domain name to set contacts for
* @param contacts Contact information to set
* @returns Success status
* @throws Error if the domain name is invalid, contacts are missing, or the API request fails
*/
async setContacts(domainName: string, contacts: IDomainSetContactsParams): Promise<boolean> {
// Validate inputs
if (!domainName) {
throw new Error('Domain name is required');
}
if (!contacts || Object.keys(contacts).length === 0) {
throw new Error('Contact information is required');
}
// Ensure at least one contact type is provided
if (!contacts.registrant && !contacts.tech && !contacts.admin && !contacts.auxBilling) {
throw new Error('At least one contact type (registrant, tech, admin, or auxBilling) is required');
}
try {
// Prepare the request parameters
const params: Record<string, string> = {
DomainName: domainName,
...this.flattenContacts(contacts)
};
// Make the API request
const response = await this.client.request(
'namecheap.domains.setContacts',
params
);
// Check for API errors
if (response.ApiResponse.$.Status !== 'OK') {
const errors = response.ApiResponse.Errors;
if (Array.isArray(errors) && errors.length > 0) {
const error = errors[0] as { Error?: string[] };
if (error.Error && error.Error.length > 0) {
throw new Error(`API Error: ${error.Error.join(', ')}`);
}
}
throw new Error('Failed to set domain contacts');
}
return true;
} catch (error) {
if (error instanceof Error) {
throw error;
}
throw new Error(`Failed to set domain contacts: ${String(error)}`);
}
}
/**
* Register a new domain
* @param params Domain registration parameters
* @returns Registration result
*/
async create(params: IDomainCreateParams): Promise<{
domain: string;
registered: boolean;
chargedAmount: number;
transactionId: number;
orderId: number;
}> {
// Prepare the request parameters
const requestParams: Record<string, string | number | boolean> = {
DomainName: params.domainName,
Years: params.years,
...this.flattenContacts(params.contacts)
};
// Add nameservers if provided
if (params.nameservers && params.nameservers.length > 0) {
requestParams.Nameservers = params.nameservers.join(',');
}
// Add additional parameters
if (params.addFreeWhoisguard !== undefined) {
requestParams.AddFreeWhoisguard = params.addFreeWhoisguard;
}
if (params.whoisguardPrivacy !== undefined) {
requestParams.WGEnabled = params.whoisguardPrivacy;
}
if (params.premiumPrice !== undefined) {
requestParams.PremiumPrice = params.premiumPrice;
}
// Make the API request
const response = await this.client.request<IDomainCreateResponse>(
'namecheap.domains.create',
requestParams
);
// Extract registration information from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const domainCreateResult = commandResponse.DomainCreateResult[0];
return {
domain: domainCreateResult.$.Domain,
registered: String(domainCreateResult.$.Registered).toLowerCase() === 'true',
chargedAmount: parseFloat(domainCreateResult.$.ChargedAmount),
transactionId: parseInt(domainCreateResult.$.TransactionID, 10),
orderId: parseInt(domainCreateResult.$.OrderID, 10)
};
}
/**
* Renew a domain registration
* @param domainName Domain name to renew
* @param years Number of years to renew for
* @param premiumPrice Optional premium price for premium domains
* @returns Renewal result
*/
async renew(domainName: string, years: number, premiumPrice?: number): Promise<IDomainRenewResult> {
// Prepare the request parameters
const params: Record<string, string | number | boolean> = {
DomainName: domainName,
Years: years
};
if (premiumPrice !== undefined) {
params.PremiumPrice = premiumPrice;
}
// Make the API request
const response = await this.client.request<IDomainRenewResponse>(
'namecheap.domains.renew',
params
);
// Extract renewal information from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const renewResult = commandResponse.DomainRenewResult[0].$;
return {
domainName: renewResult.DomainName,
domainId: parseInt(renewResult.DomainID, 10),
renewed: String(renewResult.Renewed).toLowerCase() === 'true',
chargedAmount: parseFloat(renewResult.ChargedAmount),
transactionId: parseInt(renewResult.TransactionID, 10),
orderId: parseInt(renewResult.OrderID, 10),
expireDate: renewResult.DomainDetails?.ExpiredDate || ''
};
}
/**
* Reactivate an expired domain
* @param domainName Domain name to reactivate
* @returns Reactivation result
*/
async reactivate(domainName: string): Promise<{
domain: string;
reactivated: boolean;
chargedAmount: number;
orderId: number;
transactionId: number;
}> {
// Make the API request
const response = await this.client.request(
'namecheap.domains.reactivate',
{ DomainName: domainName }
);
// Extract reactivation information from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const reactivateResult = commandResponse.DomainReactivateResult[0].$;
return {
domain: reactivateResult.Domain,
reactivated: String(reactivateResult.IsSuccess).toLowerCase() === 'true',
chargedAmount: parseFloat(reactivateResult.ChargedAmount),
orderId: parseInt(reactivateResult.OrderID, 10),
transactionId: parseInt(reactivateResult.TransactionID, 10)
};
}
/**
* Get the registrar lock status for a domain
* @param domainName Domain name to check
* @returns Lock status
*/
async getRegistrarLock(domainName: string): Promise<boolean> {
// Make the API request
const response = await this.client.request<IRegistrarLockResponse>(
'namecheap.domains.getRegistrarLock',
{ DomainName: domainName }
);
// Extract lock status from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const lockStatus = commandResponse.DomainGetRegistrarLockResult[0];
return String(lockStatus.$.RegistrarLockStatus).toLowerCase() === 'true';
}
/**
* Set the registrar lock status for a domain
* @param domainName Domain name to update
* @param lockStatus New lock status (true to lock, false to unlock)
* @returns Success status
*/
async setRegistrarLock(domainName: string, lockStatus: boolean): Promise<boolean> {
// Make the API request
const response = await this.client.request(
'namecheap.domains.setRegistrarLock',
{
DomainName: domainName,
LockAction: lockStatus ? 'LOCK' : 'UNLOCK'
}
);
// Check if the operation was successful
return response.ApiResponse.$.Status === 'OK';
}
/**
* Get a list of available TLDs
* @returns List of TLDs
*/
async getTldList(): Promise<string[]> {
// Make the API request
const response = await this.client.request<IDomainTldListResponse>(
'namecheap.domains.getTldList'
);
// Extract TLD list from the response
const commandResponse = response.ApiResponse.CommandResponse[0];
const tlds = commandResponse.Tlds[0].Tld || [];
return tlds.map(tld => tld.$.Name);
}
/**
* Helper method to parse contact information
* @param contactData Contact data from API response
* @returns Parsed contact information
*/
private parseContact(contactData: any): IContactInfo {
if (!contactData) {
return {};
}
const contact: IContactInfo = {};
// Map all contact fields
Object.keys(contactData).forEach(key => {
if (Array.isArray(contactData[key]) && contactData[key].length > 0) {
// Use type assertion to handle dynamic property assignment
(contact as any)[key] = contactData[key][0];
}
});
return contact;
}
/**
* Helper method to flatten contact information for API requests
* @param contacts Contact information object
* @returns Flattened contact parameters
*/
private flattenContacts(contacts: IDomainSetContactsParams): Record<string, string> {
const params: Record<string, string> = {};
// Process each contact type
['Registrant', 'Tech', 'Admin', 'AuxBilling'].forEach(type => {
const contactType = type.toLowerCase() as keyof typeof contacts;
const contactInfo = contacts[contactType];
if (contactInfo) {
// Ensure all required fields are present
this.validateContactInfo(contactInfo, type);
// Add each field to the params with the appropriate prefix
Object.entries(contactInfo).forEach(([field, value]) => {
if (value !== undefined && value !== null) {
params[`${type}${field}`] = String(value);
}
});
}
});
return params;
}
/**
* Validate contact information to ensure required fields are present
* @param contactInfo Contact information to validate
* @param contactType Type of contact (Registrant, Tech, Admin, AuxBilling)
* @throws Error if required fields are missing
*/
private validateContactInfo(contactInfo: IContactInfo, contactType: string): void {
// Required fields for all contact types
const requiredFields = [
'FirstName',
'LastName',
'Address1',
'City',
'StateProvince',
'PostalCode',
'Country',
'Phone',
'EmailAddress'
];
// Check for missing required fields
const missingFields = requiredFields.filter(field => {
return !contactInfo[field as keyof IContactInfo];
});
if (missingFields.length > 0) {
throw new Error(
`Missing required fields for ${contactType} contact: ${missingFields.join(', ')}`
);
}
}
}