/** * 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 = {}; // 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( '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 { // 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( '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 { const params: Record = { DomainName: domainName }; if (hostName) { params.HostName = hostName; } // Make the API request const response = await this.client.request( '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 { if (!domainName) { throw new Error('Domain name is required'); } try { // Make the API request const response = await this.client.request( '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 { // 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 = { 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 = { 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( '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 { // Prepare the request parameters const params: Record = { DomainName: domainName, Years: years }; if (premiumPrice !== undefined) { params.PremiumPrice = premiumPrice; } // Make the API request const response = await this.client.request( '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 { // Make the API request const response = await this.client.request( '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 { // 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 { // Make the API request const response = await this.client.request( '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 { const params: Record = {}; // 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(', ')}` ); } } }