EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
enom.js
Go to the documentation of this file.
1// services/enom.js
2// eNom Domain Registrar API Service
3// Documentation: https://www.enom.com/api/
4
5const axios = require('axios');
6
7/**
8 * Configure eNom API credentials and base URL
9 */
10function getConfig() {
11 const isProduction = process.env.NODE_ENV === 'production';
12
13 // eNom uses UID (reseller ID) and Password
14 const uid = process.env.ENOM_UID || process.env.ENOM_USERNAME;
15 const password = process.env.ENOM_PASSWORD;
16 const baseURL = process.env.ENOM_BASE_URL || 'https://reseller.enom.com/interface.asp';
17
18 if (!uid || !password) {
19 console.warn('[eNom] API credentials not configured - some operations may fail');
20 } else if (isProduction) {
21 console.log('[eNom] Using PRODUCTION API');
22 } else {
23 console.log('[eNom] Using configured API credentials');
24 }
25
26 return {
27 baseURL,
28 uid,
29 password
30 };
31}
32
33/**
34 * Make a request to the eNom API
35 * @param {string} command - API command (e.g., 'Check', 'GetDomainInfo')
36 * @param {object} params - Additional parameters
37 * @returns {Promise<object>} - API response
38 */
39async function makeRequest(command, params = {}) {
40 const config = getConfig();
41
42 if (!config.uid || !config.password) {
43 throw new Error('[eNom] API credentials not configured. Set ENOM_UID and ENOM_PASSWORD in environment variables.');
44 }
45
46 try {
47 console.log(`[eNom] Making ${command} request with params:`, { ...params, UID: config.uid, PW: '***' });
48
49 const response = await axios.get(config.baseURL, {
50 params: {
51 UID: config.uid,
52 PW: config.password,
53 Command: command,
54 ResponseType: 'json',
55 ...params
56 },
57 timeout: 30000
58 });
59
60 console.log('[eNom] Raw response:', typeof response.data, response.data);
61
62 // Check if response is a string (error response)
63 if (typeof response.data === 'string') {
64 console.error('[eNom] API returned string instead of JSON:', response.data);
65 throw new Error(`eNom API returned unexpected format: ${response.data.substring(0, 200)}`);
66 }
67
68 // eNom wraps responses in 'interface-response' object
69 let data = response.data;
70 if (data['interface-response']) {
71 data = data['interface-response'];
72 }
73
74 // eNom returns JSON with ErrCount and errors array
75 if (data.ErrCount && parseInt(data.ErrCount) > 0) {
76 const errorMsg = data.errors?.join(', ') || data.Err1 || 'Unknown eNom API error';
77 console.error('[eNom] API returned error:', errorMsg, 'Full data:', data);
78 throw new Error(`eNom API Error: ${errorMsg}`);
79 }
80
81 return data;
82 } catch (error) {
83 if (error.response) {
84 console.error(`[eNom] API Error on ${command}:`, error.response.status, error.response.data);
85 } else {
86 console.error(`[eNom] API Error on ${command}:`, error.message);
87 }
88 throw error;
89 }
90}
91
92/**
93 * Check domain availability
94 * @param {string} domain - Domain name to check
95 * @returns {Promise<object>} - Availability info
96 */
97async function checkDomain(domain) {
98 try {
99 // Split domain into SLD and TLD
100 const parts = domain.split('.');
101 const tld = parts.pop();
102 const sld = parts.join('.');
103
104 const result = await makeRequest('Check', {
105 SLD: sld,
106 TLD: tld
107 });
108
109 // RRPCode meanings:
110 // 210 = Available
111 // 211 = Not available (registered)
112 // 212 = Invalid domain
113 // 213 = Premium domain (available but premium price)
114 const rrpCode = result.RRPCode;
115 const available = rrpCode === '210' || rrpCode === '213';
116 const isPremium = rrpCode === '213';
117
118 return {
119 domain: result.DomainName || domain,
120 available: available,
121 isPremium: isPremium,
122 status: result.RRPText || 'unknown',
123 rrpCode: result.RRPCode,
124 raw: result
125 };
126 } catch (error) {
127 console.error('[eNom] Error checking domain:', error.message);
128 throw error;
129 }
130}
131
132/**
133 * Get domain information
134 * @param {string} domain - Domain name
135 * @returns {Promise<object>} - Domain details
136 */
137async function getDomainInfo(domain) {
138 try {
139 // Split domain into SLD and TLD
140 const parts = domain.split('.');
141 const tld = parts.pop();
142 const sld = parts.join('.');
143
144 const result = await makeRequest('GetDomainInfo', {
145 SLD: sld,
146 TLD: tld
147 });
148
149 // Parse expiration date
150 let expirationDate = null;
151 if (result.GetDomainInfo?.expiration) {
152 expirationDate = new Date(result.GetDomainInfo.expiration);
153 }
154
155 // Parse nameservers
156 const nameservers = [];
157 if (result.GetDomainInfo?.services?.dns) {
158 const dnsInfo = result.GetDomainInfo.services.dns;
159 for (let i = 1; i <= 12; i++) {
160 const ns = dnsInfo[`ns${i}`];
161 if (ns) nameservers.push(ns);
162 }
163 }
164
165 return {
166 domain: domain,
167 status: result.GetDomainInfo?.['domain-status'] || 'unknown',
168 registration_date: result.GetDomainInfo?.['registration-date'] || null,
169 expiration_date: expirationDate ? expirationDate.toISOString() : null,
170 auto_renew: result.GetDomainInfo?.['auto-renew'] === 'True',
171 nameservers: nameservers,
172 registrar: 'enom',
173 raw: result
174 };
175 } catch (error) {
176 console.error('[eNom] Error getting domain info:', error.message);
177 throw error;
178 }
179}
180
181/**
182 * Update domain nameservers
183 * @param {string} domain - Domain name
184 * @param {Array<string>} nameservers - Array of nameserver hostnames (2-12 nameservers)
185 * @returns {Promise<object>} - Update result
186 */
187async function updateNameservers(domain, nameservers) {
188 try {
189 if (!nameservers || nameservers.length < 2) {
190 throw new Error('At least 2 nameservers are required');
191 }
192
193 if (nameservers.length > 12) {
194 throw new Error('Maximum 12 nameservers allowed');
195 }
196
197 // Split domain into SLD and TLD
198 const parts = domain.split('.');
199 const tld = parts.pop();
200 const sld = parts.join('.');
201
202 // Build nameserver parameters
203 const nsParams = {};
204 nameservers.forEach((ns, index) => {
205 nsParams[`NS${index + 1}`] = ns;
206 });
207
208 const result = await makeRequest('ModifyNS', {
209 SLD: sld,
210 TLD: tld,
211 ...nsParams
212 });
213
214 console.log(`[eNom] Successfully updated nameservers for ${domain}`);
215
216 return {
217 domain: domain,
218 success: true,
219 nameservers: nameservers,
220 message: result.RRPText || 'Nameservers updated successfully',
221 raw: result
222 };
223 } catch (error) {
224 console.error('[eNom] Error updating nameservers:', error.message);
225 throw error;
226 }
227}
228
229/**
230 * Get nameservers for a domain
231 * @param {string} domain - Domain name
232 * @returns {Promise<Array<string>>} - Array of nameserver hostnames
233 */
234async function getNameservers(domain) {
235 try {
236 const info = await getDomainInfo(domain);
237 return info.nameservers || [];
238 } catch (error) {
239 console.error('[eNom] Error getting nameservers:', error.message);
240 throw error;
241 }
242}
243
244/**
245 * Check multiple domains at once
246 * @param {Array<string>} domains - Array of domain names
247 * @returns {Promise<Array<object>>} - Array of availability info
248 */
249async function checkDomains(domains) {
250 try {
251 // Check domains sequentially to avoid rate limiting
252 const results = [];
253 for (const domain of domains) {
254 try {
255 const result = await checkDomain(domain);
256 results.push(result);
257 } catch (error) {
258 results.push({
259 domain: domain,
260 available: false,
261 status: 'error',
262 error: error.message
263 });
264 }
265 }
266 return results;
267 } catch (error) {
268 console.error('[eNom] Error checking multiple domains:', error.message);
269 throw error;
270 }
271}
272
273/**
274 * Register a domain
275 * @param {object} params - Registration parameters
276 * @returns {Promise<object>} - Registration result
277 */
278async function registerDomain(params) {
279 const {
280 domain,
281 years = 1,
282 nameservers = [],
283 // Contact info
284 registrantFirstName,
285 registrantLastName,
286 registrantEmail,
287 registrantPhone,
288 registrantAddress1,
289 registrantCity,
290 registrantStateProvince,
291 registrantPostalCode,
292 registrantCountry
293 } = params;
294
295 if (!registrantEmail || !registrantFirstName || !registrantLastName) {
296 throw new Error('Registrant contact information is required');
297 }
298
299 console.log('[eNom] registerDomain called with:', {
300 domain,
301 years,
302 nameservers,
303 contact: {
304 firstName: registrantFirstName,
305 lastName: registrantLastName,
306 email: registrantEmail,
307 phone: registrantPhone,
308 address: registrantAddress1,
309 city: registrantCity,
310 state: registrantStateProvince,
311 postalCode: registrantPostalCode,
312 country: registrantCountry
313 }
314 });
315
316 try {
317 // Split domain into SLD and TLD
318 const parts = domain.split('.');
319 const tld = parts.pop();
320 const sld = parts.join('.');
321
322 // Build nameserver parameters
323 const nsParams = {};
324 if (nameservers.length > 0) {
325 nameservers.forEach((ns, index) => {
326 nsParams[`NS${index + 1}`] = ns;
327 });
328 }
329
330 // Build purchase parameters
331 const purchaseParams = {
332 SLD: sld,
333 TLD: tld,
334 NumYears: years,
335 RegistrantFirstName: registrantFirstName,
336 RegistrantLastName: registrantLastName,
337 RegistrantEmailAddress: registrantEmail,
338 RegistrantPhone: registrantPhone,
339 RegistrantAddress1: registrantAddress1,
340 RegistrantCity: registrantCity,
341 RegistrantStateProvince: registrantStateProvince,
342 RegistrantPostalCode: registrantPostalCode,
343 RegistrantCountry: registrantCountry,
344 ...nsParams
345 };
346
347 // Add Australian domain extended attributes (required for .au domains)
348 // Default ABN: 20900142617 (Brent Stokes, QLD)
349 if (tld.toLowerCase().includes('.au') || tld.toLowerCase() === 'au') {
350 const abn = params.abn || '20900142617'; // Use provided ABN or default
351 console.log('[eNom] Adding Australian extended attributes - ABN:', abn);
352
353 purchaseParams['X-AU-REGISTRANT-ID-TYPE'] = 'ABN';
354 purchaseParams['X-AU-REGISTRANT-ID-NUMBER'] = abn;
355
356 // Optional: eligibility type and name (not always required)
357 if (params.eligibilityType) {
358 purchaseParams['X-ELIGIBILITY-TYPE'] = params.eligibilityType;
359 }
360 if (params.eligibilityName) {
361 purchaseParams['X-ELIGIBILITY-NAME'] = params.eligibilityName;
362 }
363 }
364
365 const result = await makeRequest('Purchase', purchaseParams);
366
367 console.log('[eNom] Purchase API result:', JSON.stringify(result, null, 2));
368
369 // Check for Order ID in response
370 if (!result.OrderID && !result.orderid && !result.OrderId) {
371 console.error('[eNom] No Order ID found in response. Full result:', result);
372 throw new Error(`eNom API did not return an Order ID. Response: ${JSON.stringify(result)}`);
373 }
374
375 const expirationDate = new Date();
376 expirationDate.setFullYear(expirationDate.getFullYear() + years);
377
378 return {
379 domain: domain,
380 status: 'active',
381 registration_date: new Date().toISOString(),
382 expiration_date: expirationDate.toISOString(),
383 registrar: 'enom',
384 order_id: result.OrderID || result.orderid || result.OrderId,
385 raw: result
386 };
387 } catch (error) {
388 console.error('[eNom] Error registering domain:', error.message);
389 throw error;
390 }
391}
392
393/**
394 * Lock domain (enable transfer lock/registrar lock)
395 * @param {string} domain - Domain name
396 * @returns {Promise<object>} - Lock result
397 */
398async function lockDomain(domain) {
399 try {
400 const parts = domain.split('.');
401 const tld = parts.pop();
402 const sld = parts.join('.');
403
404 const result = await makeRequest('SetRegLock', {
405 SLD: sld,
406 TLD: tld,
407 UnlockRegistrar: '0' // 0 = lock, 1 = unlock
408 });
409
410 return {
411 domain: domain,
412 locked: true,
413 success: result.ErrCount === '0',
414 raw: result
415 };
416 } catch (error) {
417 console.error('[eNom] Error locking domain:', error.message);
418 throw error;
419 }
420}
421
422/**
423 * Unlock domain (disable transfer lock/registrar lock)
424 * @param {string} domain - Domain name
425 * @returns {Promise<object>} - Unlock result
426 */
427async function unlockDomain(domain) {
428 try {
429 const parts = domain.split('.');
430 const tld = parts.pop();
431 const sld = parts.join('.');
432
433 const result = await makeRequest('SetRegLock', {
434 SLD: sld,
435 TLD: tld,
436 UnlockRegistrar: '1' // 0 = lock, 1 = unlock
437 });
438
439 return {
440 domain: domain,
441 locked: false,
442 success: result.ErrCount === '0',
443 raw: result
444 };
445 } catch (error) {
446 console.error('[eNom] Error unlocking domain:', error.message);
447 throw error;
448 }
449}
450
451/**
452 * Get registrar lock status
453 * @param {string} domain - Domain name
454 * @returns {Promise<object>} - Lock status
455 */
456async function getRegLock(domain) {
457 try {
458 const parts = domain.split('.');
459 const tld = parts.pop();
460 const sld = parts.join('.');
461
462 const result = await makeRequest('GetRegLock', {
463 SLD: sld,
464 TLD: tld
465 });
466
467 return {
468 domain: domain,
469 locked: result['reg-lock'] === '1',
470 raw: result
471 };
472 } catch (error) {
473 console.error('[eNom] Error getting lock status:', error.message);
474 throw error;
475 }
476}
477
478/**
479 * Get domain auth code (EPP code for transfers)
480 * @param {string} domain - Domain name
481 * @returns {Promise<object>} - Auth code result
482 */
483async function getAuthCode(domain) {
484 try {
485 const parts = domain.split('.');
486 const tld = parts.pop();
487 const sld = parts.join('.');
488
489 const result = await makeRequest('GetDomainAuthInfo', {
490 SLD: sld,
491 TLD: tld
492 });
493
494 return {
495 domain: domain,
496 auth_code: result.DomainAuthInfo || null,
497 success: result.ErrCount === '0',
498 raw: result
499 };
500 } catch (error) {
501 console.error('[eNom] Error getting auth code:', error.message);
502 throw error;
503 }
504}
505
506/**
507 * Enable/disable auto-renewal
508 * @param {string} domain - Domain name
509 * @param {boolean} enabled - Enable or disable auto-renewal
510 * @returns {Promise<object>} - Auto-renewal update result
511 */
512async function setAutoRenew(domain, enabled) {
513 try {
514 const parts = domain.split('.');
515 const tld = parts.pop();
516 const sld = parts.join('.');
517
518 const result = await makeRequest('SetRenew', {
519 SLD: sld,
520 TLD: tld,
521 RenewFlag: enabled ? '1' : '0'
522 });
523
524 return {
525 domain: domain,
526 auto_renew: enabled,
527 success: result.ErrCount === '0',
528 raw: result
529 };
530 } catch (error) {
531 console.error('[eNom] Error setting auto-renew:', error.message);
532 throw error;
533 }
534}
535
536/**
537 * Get domain contact information
538 * @param {string} domain - Domain name
539 * @returns {Promise<object>} - Contact information
540 */
541async function getContacts(domain) {
542 try {
543 const parts = domain.split('.');
544 const tld = parts.pop();
545 const sld = parts.join('.');
546
547 const result = await makeRequest('GetContacts', {
548 SLD: sld,
549 TLD: tld
550 });
551
552 return {
553 domain: domain,
554 registrant: result.Registrant || null,
555 admin: result.Admin || null,
556 tech: result.Tech || null,
557 billing: result.AuxBilling || null,
558 raw: result
559 };
560 } catch (error) {
561 console.error('[eNom] Error getting contacts:', error.message);
562 throw error;
563 }
564}
565
566/**
567 * Renew domain
568 * @param {string} domain - Domain name
569 * @param {number} years - Number of years to renew
570 * @returns {Promise<object>} - Renewal result
571 */
572async function renewDomain(domain, years = 1) {
573 try {
574 const parts = domain.split('.');
575 const tld = parts.pop();
576 const sld = parts.join('.');
577
578 const result = await makeRequest('Extend', {
579 SLD: sld,
580 TLD: tld,
581 NumYears: years
582 });
583
584 return {
585 domain: domain,
586 renewed: true,
587 years: years,
588 success: result.ErrCount === '0',
589 raw: result
590 };
591 } catch (error) {
592 console.error('[eNom] Error renewing domain:', error.message);
593 throw error;
594 }
595}
596
597/**
598 * Get all domains in the eNom account
599 * @returns {Promise<Array>} - List of all domains
600 */
601async function getAllDomains() {
602 try {
603 console.log('[eNom] Fetching all domains from account');
604
605 // eNom GetDomains command returns all domains in account
606 const result = await makeRequest('GetDomains', {});
607
608 if (!result || !result.DomainDetail) {
609 console.log('[eNom] No domains found or unexpected response format');
610 return [];
611 }
612
613 // Handle both single domain (object) and multiple domains (array)
614 let domains = [];
615 if (Array.isArray(result.DomainDetail)) {
616 domains = result.DomainDetail;
617 } else if (result.DomainDetail) {
618 domains = [result.DomainDetail];
619 }
620
621 console.log(`[eNom] Found ${domains.length} domains`);
622
623 // Map to consistent format
624 return domains.map(domain => ({
625 name: domain.domainname || domain.sld + '.' + domain.tld,
626 status: domain.expstatus || domain.status,
627 expiration_date: domain.expiration_date || domain['expiration-date'],
628 registration_date: domain.registration_date || domain['registration-date'],
629 auto_renew: domain['auto-renew'] === '1' || domain.AutoRenew === '1',
630 locked: domain['registrar-lock'] === '1',
631 raw: domain
632 }));
633 } catch (error) {
634 console.error('[eNom] Error getting all domains:', error.message);
635 // Return empty array on error rather than throwing
636 return [];
637 }
638}
639
640module.exports = {
641 checkDomain,
642 checkDomains,
643 getDomainInfo,
644 updateNameservers,
645 getNameservers,
646 registerDomain,
647 lockDomain,
648 unlockDomain,
649 getRegLock,
650 getAuthCode,
651 setAutoRenew,
652 getContacts,
653 renewDomain,
654 getAllDomains,
655 makeRequest
656};