@startanaicompany/dns 1.3.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 +435 -0
- package/bin/saac_dns.js +1340 -0
- package/index.js +675 -0
- package/lib/client.js +134 -0
- package/lib/namesilo.js +754 -0
- package/package.json +27 -0
package/index.js
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @startanaicompany/saac_dns
|
|
5
|
+
* Programmatic Domain Registration, DNS Management & CLI Toolkit
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const dns = require('@startanaicompany/saac_dns');
|
|
9
|
+
* // Set env vars: SAAC_USER_API_KEY and SAAC_USER_EMAIL
|
|
10
|
+
* const results = await dns.search(['mycompany.com', 'mycompany.io']);
|
|
11
|
+
* await dns.buy('mycompany.io', { years: 1, privacy: true, autoRenew: true });
|
|
12
|
+
* await dns.records.add('mycompany.io', { type: 'A', host: '@', value: '1.2.3.4' });
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { request, ensureRegistered } = require('./lib/client');
|
|
16
|
+
|
|
17
|
+
// ─── Domain Search & Registration ──────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Search domain availability and pricing
|
|
21
|
+
* @param {string[]} domains - Array of domain names to search
|
|
22
|
+
* @returns {Promise<Array<{fqdn, available, price, currency, tld}>>}
|
|
23
|
+
*/
|
|
24
|
+
async function search(domains) {
|
|
25
|
+
if (!Array.isArray(domains)) domains = [domains];
|
|
26
|
+
// Validate domain format
|
|
27
|
+
const DOMAIN_REGEX = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
28
|
+
for (const domain of domains) {
|
|
29
|
+
if (!domain || typeof domain !== 'string') {
|
|
30
|
+
const err = new Error(`Invalid domain: "${domain}". Domain must be a non-empty string.`);
|
|
31
|
+
err.name = 'ValidationError';
|
|
32
|
+
err.code = 108;
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
if (!DOMAIN_REGEX.test(domain.trim())) {
|
|
36
|
+
const err = new Error(`Invalid domain format: "${domain}". Must be a valid FQDN like "example.com".`);
|
|
37
|
+
err.name = 'ValidationError';
|
|
38
|
+
err.code = 108;
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const domain of domains) {
|
|
44
|
+
const data = await request('GET', `/v1/domains/search?q=${encodeURIComponent(domain.trim())}`);
|
|
45
|
+
results.push(...(data.results || []));
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Register a domain
|
|
52
|
+
* @param {string} domain - FQDN to register (e.g. 'mycompany.io')
|
|
53
|
+
* @param {Object} opts - Options: years, privacy, autoRenew
|
|
54
|
+
*/
|
|
55
|
+
async function buy(domain, opts = {}) {
|
|
56
|
+
const { years = 1, privacy = false, autoRenew = true } = opts;
|
|
57
|
+
return request('POST', '/v1/domains/register', {
|
|
58
|
+
fqdn: domain,
|
|
59
|
+
years,
|
|
60
|
+
privacy,
|
|
61
|
+
auto_renew: autoRenew
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Confirm a pending domain purchase using the token from buy()
|
|
67
|
+
* @param {string} token - Confirmation token received from buy()
|
|
68
|
+
*/
|
|
69
|
+
async function confirmPurchase(token) {
|
|
70
|
+
if (!token) throw new Error('token is required');
|
|
71
|
+
return request('POST', '/v1/domains/confirm', { token });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Register an expiring/dropped domain (drop-catching)
|
|
76
|
+
* @param {string} domain - FQDN to catch
|
|
77
|
+
* @param {Object} opts - years, privacy, autoRenew
|
|
78
|
+
*/
|
|
79
|
+
async function buyDrop(domain, opts = {}) {
|
|
80
|
+
const { years = 1, privacy = false, autoRenew = true } = opts;
|
|
81
|
+
return request('POST', '/v1/domains/drop-register', { fqdn: domain, years, privacy, auto_renew: autoRenew });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* List all domains for the authenticated user
|
|
86
|
+
*/
|
|
87
|
+
async function list() {
|
|
88
|
+
const data = await request('GET', '/v1/users/me/domains');
|
|
89
|
+
return data.domains || [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get domain info
|
|
94
|
+
* @param {string} domain
|
|
95
|
+
*/
|
|
96
|
+
async function info(domain) {
|
|
97
|
+
return request('GET', `/v1/domains/${domain}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get domain prices for given TLDs
|
|
102
|
+
* @param {string[]} tlds - e.g. ['.com', '.io']
|
|
103
|
+
*/
|
|
104
|
+
async function prices(tlds) {
|
|
105
|
+
const data = await request('GET', '/v1/domains/prices');
|
|
106
|
+
if (!tlds || !tlds.length) return data;
|
|
107
|
+
return Object.fromEntries(Object.entries(data).filter(([k]) => tlds.includes(k)));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Renew a domain
|
|
112
|
+
* @param {string} domain
|
|
113
|
+
* @param {number} years
|
|
114
|
+
*/
|
|
115
|
+
async function renew(domain, years = 1) {
|
|
116
|
+
return request('POST', `/v1/domains/${domain}/renew`, { years });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── DNS Records ─────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
const records = {
|
|
122
|
+
/**
|
|
123
|
+
* List DNS records for a domain
|
|
124
|
+
* @param {string} domain
|
|
125
|
+
*/
|
|
126
|
+
list(domain) {
|
|
127
|
+
return request('GET', `/v1/dns/records?domain=${encodeURIComponent(domain)}`).then(d => d.records || []);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Add a DNS record
|
|
132
|
+
* @param {string} domain
|
|
133
|
+
* @param {Object} record - {type, name/host, value, ttl, priority}
|
|
134
|
+
*/
|
|
135
|
+
add(domain, record) {
|
|
136
|
+
return request('POST', '/v1/dns/records', {
|
|
137
|
+
domain,
|
|
138
|
+
type: record.type,
|
|
139
|
+
name: record.host || record.name || '@',
|
|
140
|
+
value: record.value,
|
|
141
|
+
ttl: record.ttl || 300,
|
|
142
|
+
priority: record.priority
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Update a DNS record
|
|
148
|
+
* @param {string} domain
|
|
149
|
+
* @param {string} id - Record ID
|
|
150
|
+
* @param {Object} record - Fields to update
|
|
151
|
+
*/
|
|
152
|
+
update(domain, id, record) {
|
|
153
|
+
return request('PUT', `/v1/dns/records/${id}`, {
|
|
154
|
+
domain,
|
|
155
|
+
type: record.type,
|
|
156
|
+
name: record.host || record.name,
|
|
157
|
+
value: record.value,
|
|
158
|
+
ttl: record.ttl,
|
|
159
|
+
priority: record.priority
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Delete a DNS record
|
|
165
|
+
* @param {string} domain
|
|
166
|
+
* @param {string} id - Record ID
|
|
167
|
+
*/
|
|
168
|
+
delete(domain, id) {
|
|
169
|
+
return request('DELETE', `/v1/dns/records/${id}`);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// ─── Nameservers ─────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
const nameservers = {
|
|
176
|
+
set(domain, ns) {
|
|
177
|
+
return request('PUT', `/v1/domains/${domain}/nameservers`, { nameservers: ns });
|
|
178
|
+
},
|
|
179
|
+
list(domain) {
|
|
180
|
+
return request('GET', `/v1/domains/${domain}/nameservers`).then(d => d.nameservers || []);
|
|
181
|
+
},
|
|
182
|
+
register(domain, host, ips) {
|
|
183
|
+
return request('POST', `/v1/domains/${domain}/nameservers/registered`, { host, ips: Array.isArray(ips) ? ips : [ips] });
|
|
184
|
+
},
|
|
185
|
+
modify(domain, host, ips) {
|
|
186
|
+
return request('PUT', `/v1/domains/${domain}/nameservers/registered/${encodeURIComponent(host)}`, { ips: Array.isArray(ips) ? ips : [ips] });
|
|
187
|
+
},
|
|
188
|
+
unregister(domain, host) {
|
|
189
|
+
return request('DELETE', `/v1/domains/${domain}/nameservers/registered/${encodeURIComponent(host)}`);
|
|
190
|
+
},
|
|
191
|
+
listRegistered(domain) {
|
|
192
|
+
return request('GET', `/v1/domains/${domain}/nameservers/registered`).then(d => d.hosts || []);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// ─── Contacts ─────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
const contacts = {
|
|
199
|
+
list() {
|
|
200
|
+
return request('GET', '/v1/contacts').then(d => d.contacts || []);
|
|
201
|
+
},
|
|
202
|
+
add(contact) {
|
|
203
|
+
return request('POST', '/v1/contacts', contact);
|
|
204
|
+
},
|
|
205
|
+
update(id, contact) {
|
|
206
|
+
return request('PUT', `/v1/contacts/${id}`, contact);
|
|
207
|
+
},
|
|
208
|
+
delete(id) {
|
|
209
|
+
return request('DELETE', `/v1/contacts/${id}`);
|
|
210
|
+
},
|
|
211
|
+
associate(id, domain) {
|
|
212
|
+
return request('POST', `/v1/domains/${domain}/contact`, { contact_id: id });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ─── User Account ─────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
const account = {
|
|
219
|
+
me() {
|
|
220
|
+
return request('GET', '/v1/users/me');
|
|
221
|
+
},
|
|
222
|
+
preferences(prefs) {
|
|
223
|
+
return request('PUT', '/v1/users/me/preferences', prefs);
|
|
224
|
+
},
|
|
225
|
+
audit(page = 1, limit = 50) {
|
|
226
|
+
return request('GET', `/v1/users/me/audit?page=${page}&limit=${limit}`);
|
|
227
|
+
},
|
|
228
|
+
balance() {
|
|
229
|
+
return request('GET', '/v1/account/balance');
|
|
230
|
+
},
|
|
231
|
+
addFunds(amount, paymentId) {
|
|
232
|
+
return request('POST', '/v1/account/funds', { amount, payment_id: paymentId });
|
|
233
|
+
},
|
|
234
|
+
listPaymentMethods() {
|
|
235
|
+
return request('GET', '/v1/account/payment-methods').then(d => d.payment_methods || []);
|
|
236
|
+
},
|
|
237
|
+
rotateKey() {
|
|
238
|
+
return request('POST', '/v1/auth/rotate-key');
|
|
239
|
+
},
|
|
240
|
+
allowlist: {
|
|
241
|
+
list() { return request('GET', '/v1/auth/allowlist'); },
|
|
242
|
+
add(ip) { return request('POST', '/v1/auth/allowlist', { ip }); },
|
|
243
|
+
remove(ip) { return request('DELETE', `/v1/auth/allowlist/${encodeURIComponent(ip)}`); }
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// ─── High-level Convenience Methods ──────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Search + buy in one flow (if available)
|
|
251
|
+
* @param {string} domain
|
|
252
|
+
* @param {Object} opts
|
|
253
|
+
*/
|
|
254
|
+
async function quickBuy(domain, opts = {}) {
|
|
255
|
+
const results = await search([domain]);
|
|
256
|
+
const result = results.find(r => r.fqdn === domain);
|
|
257
|
+
if (!result) throw new Error(`Domain ${domain} not found in search results`);
|
|
258
|
+
if (!result.available) throw new Error(`Domain ${domain} is not available`);
|
|
259
|
+
return buy(domain, { autoRenew: true, privacy: true, ...opts });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Hosting provider DNS templates
|
|
263
|
+
const HOSTING_TEMPLATES = {
|
|
264
|
+
vercel: { ips: ['76.76.21.21'], cname: 'cname.vercel-dns.com' },
|
|
265
|
+
netlify: { ips: ['75.2.60.5', '99.83.190.102'], cname: 'apex-loadbalancer.netlify.com' },
|
|
266
|
+
cloudflare: { ips: ['192.0.2.1'], cname: null, note: 'Point @ A record to your Cloudflare assigned IP, then proxy via CF dashboard' },
|
|
267
|
+
railway: { ips: [], cname: 'proxy.railway.app' },
|
|
268
|
+
render: { ips: ['216.24.57.1'], cname: 'oregon-render.global.ssl.fastly.net' },
|
|
269
|
+
'github-pages': { ips: ['185.199.108.153', '185.199.109.153', '185.199.110.153', '185.199.111.153'], cname: null, note: 'Add CNAME for www pointing to your-username.github.io' }
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Set up web DNS records for a domain (A + CNAME for www)
|
|
274
|
+
* @param {string} domain
|
|
275
|
+
* @param {string} ipOrProvider - IP address or hosting provider name (vercel, netlify, cloudflare, railway, render)
|
|
276
|
+
*/
|
|
277
|
+
async function setupWeb(domain, ipOrProvider) {
|
|
278
|
+
const template = typeof ipOrProvider === 'string' ? HOSTING_TEMPLATES[ipOrProvider.toLowerCase()] : null;
|
|
279
|
+
const added = [];
|
|
280
|
+
|
|
281
|
+
if (template) {
|
|
282
|
+
// Use hosting provider template
|
|
283
|
+
for (const ip of (template.ips || [])) {
|
|
284
|
+
added.push(await records.add(domain, { type: 'A', host: '@', value: ip, ttl: 300 }));
|
|
285
|
+
}
|
|
286
|
+
if (template.cname) {
|
|
287
|
+
added.push(await records.add(domain, { type: 'CNAME', host: 'www', value: template.cname, ttl: 300 }));
|
|
288
|
+
}
|
|
289
|
+
return { message: `Web DNS configured for ${domain} (${ipOrProvider})`, records: added, note: template.note };
|
|
290
|
+
} else {
|
|
291
|
+
// Use raw IP
|
|
292
|
+
const ip = ipOrProvider;
|
|
293
|
+
if (!ip) throw new Error('IP address or hosting provider name required');
|
|
294
|
+
added.push(await records.add(domain, { type: 'A', host: '@', value: ip, ttl: 300 }));
|
|
295
|
+
added.push(await records.add(domain, { type: 'CNAME', host: 'www', value: domain, ttl: 300 }));
|
|
296
|
+
return { message: `Web DNS configured for ${domain}`, records: added };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Email provider DNS templates
|
|
301
|
+
const EMAIL_TEMPLATES = {
|
|
302
|
+
gmail: {
|
|
303
|
+
mx: [
|
|
304
|
+
{ priority: 1, value: 'aspmx.l.google.com' },
|
|
305
|
+
{ priority: 5, value: 'alt1.aspmx.l.google.com' },
|
|
306
|
+
{ priority: 5, value: 'alt2.aspmx.l.google.com' },
|
|
307
|
+
{ priority: 10, value: 'alt3.aspmx.l.google.com' },
|
|
308
|
+
{ priority: 10, value: 'alt4.aspmx.l.google.com' }
|
|
309
|
+
],
|
|
310
|
+
spf: 'v=spf1 include:_spf.google.com ~all',
|
|
311
|
+
dkimNote: 'Add DKIM TXT record from Google Workspace admin after setup'
|
|
312
|
+
},
|
|
313
|
+
office365: {
|
|
314
|
+
mx: [{ priority: 0, value: (domain) => `${domain.replace(/\./g, '-')}.mail.protection.outlook.com` }],
|
|
315
|
+
spf: 'v=spf1 include:spf.protection.outlook.com ~all',
|
|
316
|
+
dkimNote: 'Add DKIM CNAME records from Microsoft 365 admin after setup'
|
|
317
|
+
},
|
|
318
|
+
// microsoft365 is an alias for office365
|
|
319
|
+
microsoft365: {
|
|
320
|
+
mx: [{ priority: 0, value: (domain) => `${domain.replace(/\./g, '-')}.mail.protection.outlook.com` }],
|
|
321
|
+
spf: 'v=spf1 include:spf.protection.outlook.com ~all',
|
|
322
|
+
dkimNote: 'Add DKIM CNAME records from Microsoft 365 admin after setup'
|
|
323
|
+
},
|
|
324
|
+
zoho: {
|
|
325
|
+
mx: [
|
|
326
|
+
{ priority: 10, value: 'mx.zoho.com' },
|
|
327
|
+
{ priority: 20, value: 'mx2.zoho.com' },
|
|
328
|
+
{ priority: 50, value: 'mx3.zoho.com' }
|
|
329
|
+
],
|
|
330
|
+
spf: 'v=spf1 include:zoho.com ~all',
|
|
331
|
+
dkimNote: 'Add DKIM TXT record from Zoho Mail admin after setup'
|
|
332
|
+
},
|
|
333
|
+
titan: {
|
|
334
|
+
mx: [{ priority: 10, value: 'smtp.titan.email' }],
|
|
335
|
+
spf: 'v=spf1 include:spf.titan.email ~all',
|
|
336
|
+
dkimNote: 'Add DKIM TXT record from Titan admin after setup'
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Set up email DNS records (MX + SPF + DMARC) for a domain
|
|
342
|
+
* @param {string} domain
|
|
343
|
+
* @param {string} provider - Email provider: gmail, office365, zoho, titan
|
|
344
|
+
*/
|
|
345
|
+
async function setupEmail(domain, provider = 'gmail') {
|
|
346
|
+
if (!domain || typeof domain !== 'string') {
|
|
347
|
+
throw new Error('domain must be a non-empty string');
|
|
348
|
+
}
|
|
349
|
+
const tmpl = EMAIL_TEMPLATES[provider.toLowerCase()];
|
|
350
|
+
if (!tmpl) {
|
|
351
|
+
throw new Error(`Unknown email provider: ${provider}. Supported: ${Object.keys(EMAIL_TEMPLATES).join(', ')}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const added = [];
|
|
355
|
+
|
|
356
|
+
// Add MX records
|
|
357
|
+
for (const mx of tmpl.mx) {
|
|
358
|
+
const value = typeof mx.value === 'function' ? mx.value(domain) : mx.value;
|
|
359
|
+
added.push(await records.add(domain, { type: 'MX', host: '@', value, ttl: 300, priority: mx.priority }));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Add SPF TXT record
|
|
363
|
+
added.push(await records.add(domain, { type: 'TXT', host: '@', value: tmpl.spf, ttl: 300 }));
|
|
364
|
+
|
|
365
|
+
// Add DMARC TXT record
|
|
366
|
+
added.push(await records.add(domain, {
|
|
367
|
+
type: 'TXT',
|
|
368
|
+
host: '_dmarc',
|
|
369
|
+
value: `v=DMARC1; p=none; rua=mailto:dmarc@${domain}`,
|
|
370
|
+
ttl: 300
|
|
371
|
+
}));
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
message: `Email DNS configured for ${domain} (${provider})`,
|
|
375
|
+
records: added,
|
|
376
|
+
note: tmpl.dkimNote
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Clone all DNS records from one domain to another
|
|
382
|
+
* @param {string} source - Source domain FQDN
|
|
383
|
+
* @param {string} target - Target domain FQDN
|
|
384
|
+
*/
|
|
385
|
+
async function cloneDns(source, target) {
|
|
386
|
+
const sourceRecords = await records.list(source);
|
|
387
|
+
if (!sourceRecords.length) {
|
|
388
|
+
return { cloned: 0, message: `No DNS records found on ${source}` };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const results = [];
|
|
392
|
+
for (const record of sourceRecords) {
|
|
393
|
+
try {
|
|
394
|
+
results.push(await records.add(target, {
|
|
395
|
+
type: record.type,
|
|
396
|
+
host: record.name || '@',
|
|
397
|
+
value: record.value,
|
|
398
|
+
ttl: record.ttl || 300,
|
|
399
|
+
priority: record.priority
|
|
400
|
+
}));
|
|
401
|
+
} catch (e) {
|
|
402
|
+
// Skip records that fail (e.g. CNAME conflicts)
|
|
403
|
+
results.push({ error: e.message, record });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
cloned: results.filter(r => !r.error).length,
|
|
409
|
+
failed: results.filter(r => r.error).length,
|
|
410
|
+
message: `Cloned ${results.filter(r => !r.error).length} of ${sourceRecords.length} records from ${source} to ${target}`,
|
|
411
|
+
results
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Export all DNS records for a domain as JSON
|
|
417
|
+
* @param {string} domain
|
|
418
|
+
*/
|
|
419
|
+
async function exportRecords(domain) {
|
|
420
|
+
return records.list(domain);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Import DNS records for a domain from an array
|
|
425
|
+
* @param {string} domain
|
|
426
|
+
* @param {Array} recordsArr
|
|
427
|
+
*/
|
|
428
|
+
async function importRecords(domain, recordsArr) {
|
|
429
|
+
const results = [];
|
|
430
|
+
for (const record of recordsArr) {
|
|
431
|
+
results.push(await records.add(domain, record));
|
|
432
|
+
}
|
|
433
|
+
return results;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ─── Domain Lock / Unlock / Privacy / Auto-Renew ─────────────────────────
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Lock a domain to prevent unauthorized transfers
|
|
440
|
+
* @param {string} domain
|
|
441
|
+
*/
|
|
442
|
+
async function lock(domain) {
|
|
443
|
+
return request('POST', `/v1/domains/${domain}/lock`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Unlock a domain (e.g. before initiating a transfer)
|
|
448
|
+
* @param {string} domain
|
|
449
|
+
*/
|
|
450
|
+
async function unlock(domain) {
|
|
451
|
+
return request('POST', `/v1/domains/${domain}/unlock`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Toggle WHOIS privacy for a domain
|
|
456
|
+
* @param {string} domain
|
|
457
|
+
* @param {boolean} enabled
|
|
458
|
+
*/
|
|
459
|
+
async function privacy(domain, enabled) {
|
|
460
|
+
return request('POST', `/v1/domains/${domain}/privacy`, { enabled });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Toggle auto-renewal for a domain
|
|
465
|
+
* @param {string} domain
|
|
466
|
+
* @param {boolean} enabled
|
|
467
|
+
*/
|
|
468
|
+
async function autoRenew(domain, enabled) {
|
|
469
|
+
return request('POST', `/v1/domains/${domain}/auto-renew`, { enabled });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function transfer(domain, authCode, opts = {}) {
|
|
473
|
+
return request('POST', `/v1/domains/${domain}/transfer`, { auth_code: authCode, years: opts.years || 1 });
|
|
474
|
+
}
|
|
475
|
+
async function transferCheck(domain) {
|
|
476
|
+
return request('GET', `/v1/domains/${domain}/transfer-check`);
|
|
477
|
+
}
|
|
478
|
+
async function transferStatus(domain) {
|
|
479
|
+
return request('GET', `/v1/domains/${domain}/transfer-status`);
|
|
480
|
+
}
|
|
481
|
+
async function transferUpdateEpp(domain, auth) {
|
|
482
|
+
return request('POST', `/v1/domains/${domain}/transfer-update-epp`, { auth });
|
|
483
|
+
}
|
|
484
|
+
async function transferResendEmail(domain) {
|
|
485
|
+
return request('POST', `/v1/domains/${domain}/transfer-resend`);
|
|
486
|
+
}
|
|
487
|
+
async function transferResubmit(domain) {
|
|
488
|
+
return request('POST', `/v1/domains/${domain}/transfer-resubmit`);
|
|
489
|
+
}
|
|
490
|
+
async function expiring(days = 30) {
|
|
491
|
+
const data = await request('GET', `/v1/domains/expiring?days=${days}`);
|
|
492
|
+
return data.domains || [];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function authCode(domain) {
|
|
496
|
+
return request('GET', `/v1/domains/${domain}/auth-code`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
// ─── Forwarding ──────────────────────────────────────────────────────────────
|
|
501
|
+
const forwarding = {
|
|
502
|
+
set(domain, url) {
|
|
503
|
+
return request('POST', `/v1/domains/${domain}/forward`, { url });
|
|
504
|
+
},
|
|
505
|
+
setSub(domain, subdomain, url) {
|
|
506
|
+
return request('POST', `/v1/domains/${domain}/forward-sub`, { subdomain, url });
|
|
507
|
+
},
|
|
508
|
+
deleteSub(domain, subdomain) {
|
|
509
|
+
return request('DELETE', `/v1/domains/${domain}/forward-sub/${subdomain}`);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// ─── Email Forwarding ─────────────────────────────────────────────────────
|
|
514
|
+
const email = {
|
|
515
|
+
list(domain) {
|
|
516
|
+
return request('GET', `/v1/domains/${domain}/email-fwd`).then(d => d.forwards || []);
|
|
517
|
+
},
|
|
518
|
+
forward(domain, fromEmail, toEmail) {
|
|
519
|
+
return request('POST', `/v1/domains/${domain}/email-fwd`, { from_email: fromEmail, to_email: toEmail });
|
|
520
|
+
},
|
|
521
|
+
deleteForward(domain, fromEmail) {
|
|
522
|
+
return request('DELETE', `/v1/domains/${domain}/email-fwd/${encodeURIComponent(fromEmail)}`);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
// ─── Webhooks ────────────────────────────────────────────────────────────────
|
|
528
|
+
const webhooks = {
|
|
529
|
+
list() {
|
|
530
|
+
return request('GET', '/v1/webhooks').then(d => d.webhooks || []);
|
|
531
|
+
},
|
|
532
|
+
add(url, events, secret) {
|
|
533
|
+
return request('POST', '/v1/webhooks', { url, events, secret });
|
|
534
|
+
},
|
|
535
|
+
delete(id) {
|
|
536
|
+
return request('DELETE', `/v1/webhooks/${id}`);
|
|
537
|
+
},
|
|
538
|
+
update(id, opts) {
|
|
539
|
+
return request('PUT', `/v1/webhooks/${id}`, opts);
|
|
540
|
+
},
|
|
541
|
+
test(id) {
|
|
542
|
+
return request('POST', `/v1/webhooks/${id}/test`);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// ─── Portfolio ────────────────────────────────────────────────────────────────
|
|
547
|
+
const portfolio = {
|
|
548
|
+
list() {
|
|
549
|
+
return request('GET', '/v1/portfolio').then(d => d.portfolios || []);
|
|
550
|
+
},
|
|
551
|
+
create(name) {
|
|
552
|
+
return request('POST', '/v1/portfolio', { name });
|
|
553
|
+
},
|
|
554
|
+
delete(name) {
|
|
555
|
+
return request('DELETE', `/v1/portfolio/${encodeURIComponent(name)}`);
|
|
556
|
+
},
|
|
557
|
+
assign(name, domains) {
|
|
558
|
+
return request('POST', `/v1/portfolio/${encodeURIComponent(name)}/assign`, { domains: Array.isArray(domains) ? domains : [domains] });
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// ─── Orders ──────────────────────────────────────────────────────────────────
|
|
563
|
+
const orders = {
|
|
564
|
+
list() {
|
|
565
|
+
return request('GET', '/v1/orders').then(d => d.orders || []);
|
|
566
|
+
},
|
|
567
|
+
details(orderNumber) {
|
|
568
|
+
return request('GET', `/v1/orders/${encodeURIComponent(orderNumber)}`);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// ─── DNSSEC ──────────────────────────────────────────────────────────────────
|
|
573
|
+
const dnssec = {
|
|
574
|
+
list(domain) {
|
|
575
|
+
return request('GET', `/v1/domains/${domain}/dnssec`).then(d => d.records || []);
|
|
576
|
+
},
|
|
577
|
+
add(domain, record) {
|
|
578
|
+
// Normalize camelCase → snake_case so both styles work
|
|
579
|
+
const normalized = {
|
|
580
|
+
key_tag: record.key_tag ?? record.keyTag,
|
|
581
|
+
algorithm: record.algorithm,
|
|
582
|
+
digest_type: record.digest_type ?? record.digestType,
|
|
583
|
+
digest: record.digest
|
|
584
|
+
};
|
|
585
|
+
return request('POST', `/v1/domains/${domain}/dnssec`, normalized);
|
|
586
|
+
},
|
|
587
|
+
delete(domain, record) {
|
|
588
|
+
const normalized = {
|
|
589
|
+
key_tag: record.key_tag ?? record.keyTag,
|
|
590
|
+
algorithm: record.algorithm,
|
|
591
|
+
digest_type: record.digest_type ?? record.digestType,
|
|
592
|
+
digest: record.digest
|
|
593
|
+
};
|
|
594
|
+
return request('DELETE', `/v1/domains/${domain}/dnssec`, normalized);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// ─── WHOIS ────────────────────────────────────────────────────────────────────
|
|
599
|
+
async function whois(domain) {
|
|
600
|
+
return request('GET', `/v1/domains/${domain}/whois`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
// ─── Domain Push & Registrant Verification ────────────────────────────────────
|
|
605
|
+
async function push(domain, toEmail) {
|
|
606
|
+
return request('POST', `/v1/domains/${domain}/push`, { to_email: toEmail });
|
|
607
|
+
}
|
|
608
|
+
async function verificationStatus() {
|
|
609
|
+
return request('GET', '/v1/verification/status');
|
|
610
|
+
}
|
|
611
|
+
async function emailVerify(email) {
|
|
612
|
+
return request('POST', '/v1/verification/email', { email });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
module.exports = {
|
|
616
|
+
// Domain ops
|
|
617
|
+
search,
|
|
618
|
+
buy,
|
|
619
|
+
confirmPurchase,
|
|
620
|
+
list,
|
|
621
|
+
info,
|
|
622
|
+
prices,
|
|
623
|
+
renew,
|
|
624
|
+
lock,
|
|
625
|
+
unlock,
|
|
626
|
+
privacy,
|
|
627
|
+
autoRenew,
|
|
628
|
+
quickBuy,
|
|
629
|
+
transfer,
|
|
630
|
+
transferCheck,
|
|
631
|
+
transferStatus,
|
|
632
|
+
transferUpdateEpp,
|
|
633
|
+
transferResendEmail,
|
|
634
|
+
transferResubmit,
|
|
635
|
+
buyDrop,
|
|
636
|
+
authCode,
|
|
637
|
+
expiring,
|
|
638
|
+
// DNS records
|
|
639
|
+
records,
|
|
640
|
+
// Nameservers
|
|
641
|
+
nameservers,
|
|
642
|
+
// Forwarding
|
|
643
|
+
forwarding,
|
|
644
|
+
// Email forwarding
|
|
645
|
+
email,
|
|
646
|
+
// Webhooks
|
|
647
|
+
webhooks,
|
|
648
|
+
// DNSSEC
|
|
649
|
+
dnssec,
|
|
650
|
+
// WHOIS
|
|
651
|
+
whois,
|
|
652
|
+
// Contacts
|
|
653
|
+
contacts,
|
|
654
|
+
// Account
|
|
655
|
+
account,
|
|
656
|
+
// Convenience
|
|
657
|
+
setupWeb,
|
|
658
|
+
setupEmail,
|
|
659
|
+
exportRecords,
|
|
660
|
+
importRecords,
|
|
661
|
+
cloneDns,
|
|
662
|
+
// Provider templates (exported for reference)
|
|
663
|
+
HOSTING_TEMPLATES,
|
|
664
|
+
EMAIL_TEMPLATES,
|
|
665
|
+
// Portfolio
|
|
666
|
+
portfolio,
|
|
667
|
+
// Orders
|
|
668
|
+
orders,
|
|
669
|
+
// Domain push & verification
|
|
670
|
+
push,
|
|
671
|
+
verificationStatus,
|
|
672
|
+
emailVerify,
|
|
673
|
+
// Errors
|
|
674
|
+
...require('./lib/client')
|
|
675
|
+
};
|