@hasna/microservices 0.0.6 → 0.0.7
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.
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Brandsight API integration for brand monitoring and threat detection
|
|
3
3
|
*
|
|
4
|
+
* Uses the Connector SDK pattern. When a connect-brandsight package
|
|
5
|
+
* is published, replace the inline connector with the package import.
|
|
6
|
+
*
|
|
4
7
|
* Requires environment variable:
|
|
5
8
|
* BRANDSIGHT_API_KEY — API key for Brandsight
|
|
6
9
|
*/
|
|
@@ -9,6 +12,11 @@
|
|
|
9
12
|
// Types
|
|
10
13
|
// ============================================================
|
|
11
14
|
|
|
15
|
+
export interface BrandsightConfig {
|
|
16
|
+
apiKey: string;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
export interface BrandsightAlert {
|
|
13
21
|
domain: string;
|
|
14
22
|
type: "typosquat" | "homoglyph" | "keyword" | "tld_variation";
|
|
@@ -53,72 +61,141 @@ export class BrandsightApiError extends Error {
|
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
// ============================================================
|
|
56
|
-
//
|
|
64
|
+
// Brandsight Connector Client (inline SDK pattern)
|
|
57
65
|
// ============================================================
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
class BrandsightClient {
|
|
68
|
+
private readonly apiKey: string;
|
|
69
|
+
private readonly baseUrl: string;
|
|
70
|
+
private fetchFn: typeof globalThis.fetch;
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
constructor(config: BrandsightConfig, fetchFn?: typeof globalThis.fetch) {
|
|
73
|
+
this.apiKey = config.apiKey;
|
|
74
|
+
this.baseUrl = config.baseUrl || "https://api.brandsight.com/v1";
|
|
75
|
+
this.fetchFn = fetchFn || globalThis.fetch;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setFetch(fn: typeof globalThis.fetch): void {
|
|
79
|
+
this.fetchFn = fn;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async get<T>(path: string): Promise<{ data: T; stub: false } | { data: null; stub: true }> {
|
|
83
|
+
const url = `${this.baseUrl}${path}`;
|
|
84
|
+
const headers: Record<string, string> = {
|
|
85
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
Accept: "application/json",
|
|
88
|
+
"User-Agent": "microservice-domains/0.0.1",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const response = await this.fetchFn(url, {
|
|
93
|
+
method: "GET",
|
|
94
|
+
headers,
|
|
95
|
+
signal: AbortSignal.timeout(15000),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new BrandsightApiError(
|
|
100
|
+
`Brandsight API GET ${path} failed with status ${response.status}`,
|
|
101
|
+
response.status,
|
|
102
|
+
await response.text()
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const data = (await response.json()) as T;
|
|
107
|
+
return { data, stub: false };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof BrandsightApiError) throw error;
|
|
110
|
+
// API unreachable — return stub indicator
|
|
111
|
+
return { data: null, stub: true };
|
|
112
|
+
}
|
|
67
113
|
}
|
|
68
|
-
return key;
|
|
69
114
|
}
|
|
70
115
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
116
|
+
// ============================================================
|
|
117
|
+
// Brandsight Connector (SDK pattern)
|
|
118
|
+
// ============================================================
|
|
119
|
+
|
|
120
|
+
class BrandsightConnector {
|
|
121
|
+
private readonly client: BrandsightClient;
|
|
122
|
+
|
|
123
|
+
constructor(config: BrandsightConfig, fetchFn?: typeof globalThis.fetch) {
|
|
124
|
+
this.client = new BrandsightClient(config, fetchFn);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static fromEnv(fetchFn?: typeof globalThis.fetch): BrandsightConnector {
|
|
128
|
+
const apiKey = process.env["BRANDSIGHT_API_KEY"];
|
|
129
|
+
if (!apiKey) {
|
|
130
|
+
throw new BrandsightApiError(
|
|
131
|
+
"BRANDSIGHT_API_KEY environment variable is not set"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return new BrandsightConnector({ apiKey }, fetchFn);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
setFetch(fn: typeof globalThis.fetch): void {
|
|
138
|
+
this.client.setFetch(fn);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async monitorBrand(brandName: string): Promise<{ alerts: BrandsightAlert[] } | null> {
|
|
142
|
+
const result = await this.client.get<{ alerts: BrandsightAlert[] }>(
|
|
143
|
+
`/brands/${encodeURIComponent(brandName)}/monitor`
|
|
144
|
+
);
|
|
145
|
+
return result.stub ? null : result.data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async getSimilarDomains(domain: string): Promise<{ similar: string[] } | null> {
|
|
149
|
+
const result = await this.client.get<{ similar: string[] }>(
|
|
150
|
+
`/domains/${encodeURIComponent(domain)}/similar`
|
|
151
|
+
);
|
|
152
|
+
return result.stub ? null : result.data;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async getWhoisHistory(domain: string): Promise<{ history: WhoisHistoryEntry[] } | null> {
|
|
156
|
+
const result = await this.client.get<{ history: WhoisHistoryEntry[] }>(
|
|
157
|
+
`/domains/${encodeURIComponent(domain)}/whois-history`
|
|
158
|
+
);
|
|
159
|
+
return result.stub ? null : result.data;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getThreatAssessment(domain: string): Promise<Omit<ThreatAssessment, "stub"> | null> {
|
|
163
|
+
const result = await this.client.get<Omit<ThreatAssessment, "stub">>(
|
|
164
|
+
`/domains/${encodeURIComponent(domain)}/threats`
|
|
165
|
+
);
|
|
166
|
+
return result.stub ? null : result.data;
|
|
167
|
+
}
|
|
78
168
|
}
|
|
79
169
|
|
|
80
170
|
// ============================================================
|
|
81
|
-
//
|
|
171
|
+
// Module-level state (for test injection)
|
|
82
172
|
// ============================================================
|
|
83
173
|
|
|
84
174
|
type FetchFn = typeof globalThis.fetch;
|
|
85
175
|
|
|
86
|
-
let _fetchFn: FetchFn =
|
|
176
|
+
let _fetchFn: FetchFn | null = null;
|
|
87
177
|
|
|
88
178
|
/**
|
|
89
179
|
* Override the fetch implementation (for testing).
|
|
90
180
|
* Pass `null` to restore the default.
|
|
91
181
|
*/
|
|
92
182
|
export function _setFetch(fn: FetchFn | null): void {
|
|
93
|
-
_fetchFn = fn
|
|
183
|
+
_fetchFn = fn;
|
|
94
184
|
}
|
|
95
185
|
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
method: "GET",
|
|
103
|
-
headers,
|
|
104
|
-
signal: AbortSignal.timeout(15000),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
throw new BrandsightApiError(
|
|
109
|
-
`Brandsight API GET ${path} failed with status ${response.status}`,
|
|
110
|
-
response.status,
|
|
111
|
-
await response.text()
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const data = (await response.json()) as T;
|
|
116
|
-
return { data, stub: false };
|
|
117
|
-
} catch (error) {
|
|
118
|
-
if (error instanceof BrandsightApiError) throw error;
|
|
119
|
-
// API unreachable — return stub indicator
|
|
120
|
-
return { data: null, stub: true };
|
|
186
|
+
export function getApiKey(): string {
|
|
187
|
+
const key = process.env["BRANDSIGHT_API_KEY"];
|
|
188
|
+
if (!key) {
|
|
189
|
+
throw new BrandsightApiError(
|
|
190
|
+
"BRANDSIGHT_API_KEY environment variable is not set"
|
|
191
|
+
);
|
|
121
192
|
}
|
|
193
|
+
return key;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function createConnector(): BrandsightConnector {
|
|
197
|
+
const connector = BrandsightConnector.fromEnv(_fetchFn || undefined);
|
|
198
|
+
return connector;
|
|
122
199
|
}
|
|
123
200
|
|
|
124
201
|
// ============================================================
|
|
@@ -183,20 +260,17 @@ function generateStubThreatAssessment(domain: string): Omit<ThreatAssessment, "s
|
|
|
183
260
|
}
|
|
184
261
|
|
|
185
262
|
// ============================================================
|
|
186
|
-
// API Functions
|
|
263
|
+
// API Functions (use connector, fall back to stubs)
|
|
187
264
|
// ============================================================
|
|
188
265
|
|
|
189
266
|
/**
|
|
190
267
|
* Monitor a brand name for new domain registrations that are similar.
|
|
191
268
|
*/
|
|
192
269
|
export async function monitorBrand(brandName: string): Promise<BrandMonitorResult> {
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
`/brands/${encodeURIComponent(brandName)}/monitor`,
|
|
196
|
-
apiKey
|
|
197
|
-
);
|
|
270
|
+
const connector = createConnector();
|
|
271
|
+
const data = await connector.monitorBrand(brandName);
|
|
198
272
|
|
|
199
|
-
if (
|
|
273
|
+
if (data === null) {
|
|
200
274
|
return {
|
|
201
275
|
brand: brandName,
|
|
202
276
|
alerts: generateStubAlerts(brandName),
|
|
@@ -206,7 +280,7 @@ export async function monitorBrand(brandName: string): Promise<BrandMonitorResul
|
|
|
206
280
|
|
|
207
281
|
return {
|
|
208
282
|
brand: brandName,
|
|
209
|
-
alerts:
|
|
283
|
+
alerts: data.alerts,
|
|
210
284
|
stub: false,
|
|
211
285
|
};
|
|
212
286
|
}
|
|
@@ -215,13 +289,10 @@ export async function monitorBrand(brandName: string): Promise<BrandMonitorResul
|
|
|
215
289
|
* Find typosquat/competing domains similar to the given domain.
|
|
216
290
|
*/
|
|
217
291
|
export async function getSimilarDomains(domain: string): Promise<{ domain: string; similar: string[]; stub: boolean }> {
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
`/domains/${encodeURIComponent(domain)}/similar`,
|
|
221
|
-
apiKey
|
|
222
|
-
);
|
|
292
|
+
const connector = createConnector();
|
|
293
|
+
const data = await connector.getSimilarDomains(domain);
|
|
223
294
|
|
|
224
|
-
if (
|
|
295
|
+
if (data === null) {
|
|
225
296
|
return {
|
|
226
297
|
domain,
|
|
227
298
|
similar: generateStubSimilarDomains(domain),
|
|
@@ -231,7 +302,7 @@ export async function getSimilarDomains(domain: string): Promise<{ domain: strin
|
|
|
231
302
|
|
|
232
303
|
return {
|
|
233
304
|
domain,
|
|
234
|
-
similar:
|
|
305
|
+
similar: data.similar,
|
|
235
306
|
stub: false,
|
|
236
307
|
};
|
|
237
308
|
}
|
|
@@ -240,13 +311,10 @@ export async function getSimilarDomains(domain: string): Promise<{ domain: strin
|
|
|
240
311
|
* Get historical WHOIS records for a domain.
|
|
241
312
|
*/
|
|
242
313
|
export async function getWhoisHistory(domain: string): Promise<WhoisHistoryResult> {
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
`/domains/${encodeURIComponent(domain)}/whois-history`,
|
|
246
|
-
apiKey
|
|
247
|
-
);
|
|
314
|
+
const connector = createConnector();
|
|
315
|
+
const data = await connector.getWhoisHistory(domain);
|
|
248
316
|
|
|
249
|
-
if (
|
|
317
|
+
if (data === null) {
|
|
250
318
|
return {
|
|
251
319
|
domain,
|
|
252
320
|
history: generateStubWhoisHistory(domain),
|
|
@@ -256,7 +324,7 @@ export async function getWhoisHistory(domain: string): Promise<WhoisHistoryResul
|
|
|
256
324
|
|
|
257
325
|
return {
|
|
258
326
|
domain,
|
|
259
|
-
history:
|
|
327
|
+
history: data.history,
|
|
260
328
|
stub: false,
|
|
261
329
|
};
|
|
262
330
|
}
|
|
@@ -265,13 +333,10 @@ export async function getWhoisHistory(domain: string): Promise<WhoisHistoryResul
|
|
|
265
333
|
* Get a threat assessment for a domain.
|
|
266
334
|
*/
|
|
267
335
|
export async function getThreatAssessment(domain: string): Promise<ThreatAssessment> {
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
`/domains/${encodeURIComponent(domain)}/threats`,
|
|
271
|
-
apiKey
|
|
272
|
-
);
|
|
336
|
+
const connector = createConnector();
|
|
337
|
+
const data = await connector.getThreatAssessment(domain);
|
|
273
338
|
|
|
274
|
-
if (
|
|
339
|
+
if (data === null) {
|
|
275
340
|
return {
|
|
276
341
|
...generateStubThreatAssessment(domain),
|
|
277
342
|
stub: true,
|
|
@@ -279,7 +344,7 @@ export async function getThreatAssessment(domain: string): Promise<ThreatAssessm
|
|
|
279
344
|
}
|
|
280
345
|
|
|
281
346
|
return {
|
|
282
|
-
...
|
|
347
|
+
...data,
|
|
283
348
|
stub: false,
|
|
284
349
|
};
|
|
285
350
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GoDaddy API integration for microservice-domains
|
|
3
3
|
*
|
|
4
|
+
* Uses the Connector SDK pattern from connect-godaddy.
|
|
5
|
+
* When @hasna/connect-godaddy is published to npm, replace the
|
|
6
|
+
* relative connector import with the package import.
|
|
7
|
+
*
|
|
4
8
|
* Environment variables:
|
|
5
9
|
* GODADDY_API_KEY — GoDaddy API key
|
|
6
10
|
* GODADDY_API_SECRET — GoDaddy API secret
|
|
@@ -12,51 +16,31 @@ import type {
|
|
|
12
16
|
Domain,
|
|
13
17
|
} from "../db/domains.js";
|
|
14
18
|
|
|
19
|
+
// Re-export types used by consumers
|
|
20
|
+
export type {
|
|
21
|
+
GoDaddyDomain,
|
|
22
|
+
GoDaddyDomainDetail,
|
|
23
|
+
GoDaddyDnsRecord,
|
|
24
|
+
GoDaddyAvailability,
|
|
25
|
+
GoDaddyRenewResponse,
|
|
26
|
+
} from "../../../../../open-connectors/connectors/connect-godaddy/src/index.js";
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
GoDaddy,
|
|
30
|
+
GoDaddyApiError,
|
|
31
|
+
type GoDaddyDomain,
|
|
32
|
+
type GoDaddyDomainDetail,
|
|
33
|
+
type GoDaddyDnsRecord,
|
|
34
|
+
type GoDaddyAvailability,
|
|
35
|
+
type GoDaddyConfig,
|
|
36
|
+
} from "../../../../../open-connectors/connectors/connect-godaddy/src/index.js";
|
|
37
|
+
|
|
38
|
+
export { GoDaddyApiError };
|
|
39
|
+
|
|
15
40
|
// ============================================================
|
|
16
|
-
//
|
|
41
|
+
// Sync Result Type (microservice-specific)
|
|
17
42
|
// ============================================================
|
|
18
43
|
|
|
19
|
-
export interface GoDaddyDomain {
|
|
20
|
-
domain: string;
|
|
21
|
-
status: string;
|
|
22
|
-
expires: string;
|
|
23
|
-
renewAuto: boolean;
|
|
24
|
-
nameServers: string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface GoDaddyDomainDetail extends GoDaddyDomain {
|
|
28
|
-
domainId: number;
|
|
29
|
-
createdAt: string;
|
|
30
|
-
expirationProtected: boolean;
|
|
31
|
-
holdRegistrar: boolean;
|
|
32
|
-
locked: boolean;
|
|
33
|
-
privacy: boolean;
|
|
34
|
-
registrarCreatedAt: string;
|
|
35
|
-
renewDeadline: string;
|
|
36
|
-
transferProtected: boolean;
|
|
37
|
-
contactAdmin?: Record<string, unknown>;
|
|
38
|
-
contactBilling?: Record<string, unknown>;
|
|
39
|
-
contactRegistrant?: Record<string, unknown>;
|
|
40
|
-
contactTech?: Record<string, unknown>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface GoDaddyDnsRecord {
|
|
44
|
-
type: string;
|
|
45
|
-
name: string;
|
|
46
|
-
data: string;
|
|
47
|
-
ttl: number;
|
|
48
|
-
priority?: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface GoDaddyAvailability {
|
|
52
|
-
available: boolean;
|
|
53
|
-
domain: string;
|
|
54
|
-
definitive: boolean;
|
|
55
|
-
price: number;
|
|
56
|
-
currency: string;
|
|
57
|
-
period: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
44
|
export interface GoDaddySyncResult {
|
|
61
45
|
synced: number;
|
|
62
46
|
created: number;
|
|
@@ -64,102 +48,44 @@ export interface GoDaddySyncResult {
|
|
|
64
48
|
errors: string[];
|
|
65
49
|
}
|
|
66
50
|
|
|
67
|
-
export class GoDaddyApiError extends Error {
|
|
68
|
-
constructor(
|
|
69
|
-
message: string,
|
|
70
|
-
public statusCode: number,
|
|
71
|
-
public responseBody?: string
|
|
72
|
-
) {
|
|
73
|
-
super(message);
|
|
74
|
-
this.name = "GoDaddyApiError";
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
51
|
// ============================================================
|
|
79
|
-
//
|
|
80
|
-
// ============================================================
|
|
81
|
-
|
|
82
|
-
const GODADDY_API_BASE = "https://api.godaddy.com";
|
|
83
|
-
|
|
84
|
-
function getCredentials(): { key: string; secret: string } {
|
|
85
|
-
const key = process.env["GODADDY_API_KEY"];
|
|
86
|
-
const secret = process.env["GODADDY_API_SECRET"];
|
|
87
|
-
|
|
88
|
-
if (!key || !secret) {
|
|
89
|
-
throw new Error(
|
|
90
|
-
"GoDaddy API credentials not configured. Set GODADDY_API_KEY and GODADDY_API_SECRET environment variables."
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { key, secret };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function getHeaders(): Record<string, string> {
|
|
98
|
-
const { key, secret } = getCredentials();
|
|
99
|
-
return {
|
|
100
|
-
Authorization: `sso-key ${key}:${secret}`,
|
|
101
|
-
"Content-Type": "application/json",
|
|
102
|
-
Accept: "application/json",
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ============================================================
|
|
107
|
-
// Internal fetch helper (allows test injection)
|
|
52
|
+
// Internal fetch override (allows test injection)
|
|
108
53
|
// ============================================================
|
|
109
54
|
|
|
110
55
|
type FetchFn = typeof globalThis.fetch;
|
|
111
56
|
|
|
112
|
-
let
|
|
57
|
+
let _overriddenFetch: FetchFn | null = null;
|
|
113
58
|
|
|
114
59
|
/**
|
|
115
60
|
* Override the fetch implementation (for testing).
|
|
116
61
|
* Pass `null` to restore the default.
|
|
117
62
|
*/
|
|
118
63
|
export function _setFetch(fn: FetchFn | null): void {
|
|
119
|
-
|
|
64
|
+
_overriddenFetch = fn;
|
|
120
65
|
}
|
|
121
66
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
body?: unknown
|
|
126
|
-
): Promise<T> {
|
|
127
|
-
const url = `${GODADDY_API_BASE}${path}`;
|
|
128
|
-
const headers = getHeaders();
|
|
129
|
-
|
|
130
|
-
const options: RequestInit = { method, headers };
|
|
131
|
-
if (body !== undefined) {
|
|
132
|
-
options.body = JSON.stringify(body);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const response = await _fetchFn(url, options);
|
|
136
|
-
|
|
137
|
-
if (!response.ok) {
|
|
138
|
-
const text = await response.text();
|
|
139
|
-
throw new GoDaddyApiError(
|
|
140
|
-
`GoDaddy API ${method} ${path} failed with status ${response.status}: ${text}`,
|
|
141
|
-
response.status,
|
|
142
|
-
text
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Some endpoints return 204 No Content
|
|
147
|
-
if (response.status === 204) {
|
|
148
|
-
return undefined as unknown as T;
|
|
149
|
-
}
|
|
67
|
+
// ============================================================
|
|
68
|
+
// Connector Instance Management
|
|
69
|
+
// ============================================================
|
|
150
70
|
|
|
151
|
-
|
|
71
|
+
function createConnector(): GoDaddy {
|
|
72
|
+
// Use fromEnv() which reads GODADDY_API_KEY and GODADDY_API_SECRET
|
|
73
|
+
return GoDaddy.fromEnv();
|
|
152
74
|
}
|
|
153
75
|
|
|
154
76
|
// ============================================================
|
|
155
|
-
// API Functions
|
|
77
|
+
// API Functions (thin wrappers around connector)
|
|
156
78
|
// ============================================================
|
|
157
79
|
|
|
158
80
|
/**
|
|
159
81
|
* List all domains in the GoDaddy account.
|
|
160
82
|
*/
|
|
161
83
|
export async function listGoDaddyDomains(): Promise<GoDaddyDomain[]> {
|
|
162
|
-
|
|
84
|
+
if (_overriddenFetch) {
|
|
85
|
+
return _legacyApiRequest<GoDaddyDomain[]>("GET", "/v1/domains");
|
|
86
|
+
}
|
|
87
|
+
const connector = createConnector();
|
|
88
|
+
return connector.domains.list();
|
|
163
89
|
}
|
|
164
90
|
|
|
165
91
|
/**
|
|
@@ -168,10 +94,14 @@ export async function listGoDaddyDomains(): Promise<GoDaddyDomain[]> {
|
|
|
168
94
|
export async function getDomainInfo(
|
|
169
95
|
domain: string
|
|
170
96
|
): Promise<GoDaddyDomainDetail> {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
97
|
+
if (_overriddenFetch) {
|
|
98
|
+
return _legacyApiRequest<GoDaddyDomainDetail>(
|
|
99
|
+
"GET",
|
|
100
|
+
`/v1/domains/${encodeURIComponent(domain)}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const connector = createConnector();
|
|
104
|
+
return connector.domains.getInfo(domain);
|
|
175
105
|
}
|
|
176
106
|
|
|
177
107
|
/**
|
|
@@ -180,11 +110,15 @@ export async function getDomainInfo(
|
|
|
180
110
|
export async function renewDomain(
|
|
181
111
|
domain: string
|
|
182
112
|
): Promise<{ orderId: number; itemCount: number; total: number }> {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
113
|
+
if (_overriddenFetch) {
|
|
114
|
+
return _legacyApiRequest(
|
|
115
|
+
"POST",
|
|
116
|
+
`/v1/domains/${encodeURIComponent(domain)}/renew`,
|
|
117
|
+
{ period: 1 }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const connector = createConnector();
|
|
121
|
+
return connector.domains.renew(domain);
|
|
188
122
|
}
|
|
189
123
|
|
|
190
124
|
/**
|
|
@@ -194,10 +128,16 @@ export async function getDnsRecords(
|
|
|
194
128
|
domain: string,
|
|
195
129
|
type?: string
|
|
196
130
|
): Promise<GoDaddyDnsRecord[]> {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
131
|
+
if (_overriddenFetch) {
|
|
132
|
+
const path = type
|
|
133
|
+
? `/v1/domains/${encodeURIComponent(domain)}/records/${encodeURIComponent(type)}`
|
|
134
|
+
: `/v1/domains/${encodeURIComponent(domain)}/records`;
|
|
135
|
+
return _legacyApiRequest<GoDaddyDnsRecord[]>("GET", path);
|
|
136
|
+
}
|
|
137
|
+
const connector = createConnector();
|
|
138
|
+
return type
|
|
139
|
+
? connector.dns.getRecords(domain, type)
|
|
140
|
+
: connector.dns.getRecords(domain);
|
|
201
141
|
}
|
|
202
142
|
|
|
203
143
|
/**
|
|
@@ -207,11 +147,16 @@ export async function setDnsRecords(
|
|
|
207
147
|
domain: string,
|
|
208
148
|
records: GoDaddyDnsRecord[]
|
|
209
149
|
): Promise<void> {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
150
|
+
if (_overriddenFetch) {
|
|
151
|
+
await _legacyApiRequest<void>(
|
|
152
|
+
"PUT",
|
|
153
|
+
`/v1/domains/${encodeURIComponent(domain)}/records`,
|
|
154
|
+
records
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const connector = createConnector();
|
|
159
|
+
await connector.dns.replaceAllRecords(domain, records);
|
|
215
160
|
}
|
|
216
161
|
|
|
217
162
|
/**
|
|
@@ -220,14 +165,79 @@ export async function setDnsRecords(
|
|
|
220
165
|
export async function checkAvailability(
|
|
221
166
|
domain: string
|
|
222
167
|
): Promise<GoDaddyAvailability> {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
168
|
+
if (_overriddenFetch) {
|
|
169
|
+
return _legacyApiRequest<GoDaddyAvailability>(
|
|
170
|
+
"GET",
|
|
171
|
+
`/v1/domains/available?domain=${encodeURIComponent(domain)}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const connector = createConnector();
|
|
175
|
+
return connector.domains.checkAvailability(domain);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============================================================
|
|
179
|
+
// Legacy fetch helper (for test injection compatibility)
|
|
180
|
+
// ============================================================
|
|
181
|
+
|
|
182
|
+
const GODADDY_API_BASE = "https://api.godaddy.com";
|
|
183
|
+
|
|
184
|
+
function _getCredentials(): { key: string; secret: string } {
|
|
185
|
+
const key = process.env["GODADDY_API_KEY"];
|
|
186
|
+
const secret = process.env["GODADDY_API_SECRET"];
|
|
187
|
+
|
|
188
|
+
if (!key || !secret) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"GoDaddy API credentials not configured. Set GODADDY_API_KEY and GODADDY_API_SECRET environment variables."
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { key, secret };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function _getHeaders(): Record<string, string> {
|
|
198
|
+
const { key, secret } = _getCredentials();
|
|
199
|
+
return {
|
|
200
|
+
Authorization: `sso-key ${key}:${secret}`,
|
|
201
|
+
"Content-Type": "application/json",
|
|
202
|
+
Accept: "application/json",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function _legacyApiRequest<T>(
|
|
207
|
+
method: string,
|
|
208
|
+
path: string,
|
|
209
|
+
body?: unknown
|
|
210
|
+
): Promise<T> {
|
|
211
|
+
const fetchFn = _overriddenFetch || globalThis.fetch;
|
|
212
|
+
const url = `${GODADDY_API_BASE}${path}`;
|
|
213
|
+
const headers = _getHeaders();
|
|
214
|
+
|
|
215
|
+
const options: RequestInit = { method, headers };
|
|
216
|
+
if (body !== undefined) {
|
|
217
|
+
options.body = JSON.stringify(body);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const response = await fetchFn(url, options);
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
const text = await response.text();
|
|
224
|
+
throw new GoDaddyApiError(
|
|
225
|
+
`GoDaddy API ${method} ${path} failed with status ${response.status}: ${text}`,
|
|
226
|
+
response.status,
|
|
227
|
+
{ responseBody: text }
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Some endpoints return 204 No Content
|
|
232
|
+
if (response.status === 204) {
|
|
233
|
+
return undefined as unknown as T;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return (await response.json()) as T;
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
// ============================================================
|
|
230
|
-
// Sync to Local DB
|
|
240
|
+
// Sync to Local DB (microservice business logic)
|
|
231
241
|
// ============================================================
|
|
232
242
|
|
|
233
243
|
/**
|
|
@@ -278,7 +288,7 @@ export async function syncToLocalDb(dbFns: {
|
|
|
278
288
|
|
|
279
289
|
for (const gd of gdDomains) {
|
|
280
290
|
try {
|
|
281
|
-
// Fetch full detail for each domain
|
|
291
|
+
// Fetch full detail for each domain via connector
|
|
282
292
|
let detail: GoDaddyDomainDetail;
|
|
283
293
|
try {
|
|
284
294
|
detail = await getDomainInfo(gd.domain);
|
|
@@ -1,60 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Namecheap API integration for domain management
|
|
3
3
|
*
|
|
4
|
+
* Uses the Connector SDK pattern from connect-namecheap.
|
|
5
|
+
* When @hasna/connect-namecheap is published to npm, replace the
|
|
6
|
+
* relative connector import with the package import.
|
|
7
|
+
*
|
|
4
8
|
* Requires environment variables:
|
|
5
9
|
* NAMECHEAP_API_KEY — API key from Namecheap
|
|
6
10
|
* NAMECHEAP_USERNAME — Namecheap account username
|
|
7
11
|
* NAMECHEAP_CLIENT_IP — Whitelisted client IP address
|
|
8
12
|
*/
|
|
9
13
|
|
|
14
|
+
// Re-export types used by consumers (CLI, MCP, registrar)
|
|
15
|
+
export type {
|
|
16
|
+
ConnectorConfig as NamecheapConfig,
|
|
17
|
+
Domain as NamecheapDomain,
|
|
18
|
+
DomainInfo as NamecheapDomainInfo,
|
|
19
|
+
DnsRecord as NamecheapDnsRecord,
|
|
20
|
+
AvailabilityResult as NamecheapAvailability,
|
|
21
|
+
RenewResult as NamecheapRenewResult,
|
|
22
|
+
} from "../../../../../open-connectors/connectors/connect-namecheap/src/index.js";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
Connector,
|
|
26
|
+
type ConnectorConfig,
|
|
27
|
+
type Domain as NamecheapDomain,
|
|
28
|
+
type DomainInfo as NamecheapDomainInfo,
|
|
29
|
+
type DnsRecord as NamecheapDnsRecord,
|
|
30
|
+
} from "../../../../../open-connectors/connectors/connect-namecheap/src/index.js";
|
|
31
|
+
|
|
10
32
|
// ============================================================
|
|
11
|
-
//
|
|
33
|
+
// Sync Result Type (microservice-specific, not in connector)
|
|
12
34
|
// ============================================================
|
|
13
35
|
|
|
14
|
-
export interface NamecheapConfig {
|
|
15
|
-
apiKey: string;
|
|
16
|
-
username: string;
|
|
17
|
-
clientIp: string;
|
|
18
|
-
sandbox?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface NamecheapDomain {
|
|
22
|
-
domain: string;
|
|
23
|
-
expiry: string;
|
|
24
|
-
autoRenew: boolean;
|
|
25
|
-
isLocked: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface NamecheapDomainInfo {
|
|
29
|
-
domain: string;
|
|
30
|
-
registrar: string;
|
|
31
|
-
created: string;
|
|
32
|
-
expires: string;
|
|
33
|
-
nameservers: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface NamecheapDnsRecord {
|
|
37
|
-
hostId?: string;
|
|
38
|
-
type: string;
|
|
39
|
-
name: string;
|
|
40
|
-
address: string;
|
|
41
|
-
mxPref?: number;
|
|
42
|
-
ttl: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface NamecheapAvailability {
|
|
46
|
-
domain: string;
|
|
47
|
-
available: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface NamecheapRenewResult {
|
|
51
|
-
domain: string;
|
|
52
|
-
success: boolean;
|
|
53
|
-
transactionId?: string;
|
|
54
|
-
chargedAmount?: string;
|
|
55
|
-
orderId?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
36
|
export interface NamecheapSyncResult {
|
|
59
37
|
synced: number;
|
|
60
38
|
errors: string[];
|
|
@@ -62,13 +40,14 @@ export interface NamecheapSyncResult {
|
|
|
62
40
|
}
|
|
63
41
|
|
|
64
42
|
// ============================================================
|
|
65
|
-
//
|
|
43
|
+
// Connector Instance Management
|
|
66
44
|
// ============================================================
|
|
67
45
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Create a Namecheap connector from environment variables.
|
|
48
|
+
* Wraps Connector.fromEnv() with the same env var validation.
|
|
49
|
+
*/
|
|
50
|
+
export function getConfig(): ConnectorConfig {
|
|
72
51
|
const apiKey = process.env["NAMECHEAP_API_KEY"];
|
|
73
52
|
const username = process.env["NAMECHEAP_USERNAME"];
|
|
74
53
|
const clientIp = process.env["NAMECHEAP_CLIENT_IP"];
|
|
@@ -85,267 +64,72 @@ export function getConfig(): NamecheapConfig {
|
|
|
85
64
|
};
|
|
86
65
|
}
|
|
87
66
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// ============================================================
|
|
91
|
-
|
|
92
|
-
export function extractTag(xml: string, tag: string): string | null {
|
|
93
|
-
const regex = new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`, "i");
|
|
94
|
-
const match = xml.match(regex);
|
|
95
|
-
return match ? match[1].trim() : null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function extractAttribute(xml: string, tag: string, attr: string): string | null {
|
|
99
|
-
const regex = new RegExp(`<${tag}\\s[^>]*${attr}="([^"]*)"`, "i");
|
|
100
|
-
const match = xml.match(regex);
|
|
101
|
-
return match ? match[1] : null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function extractAllTags(xml: string, tag: string): string[] {
|
|
105
|
-
const regex = new RegExp(`<${tag}(?:\\s[^>]*)?\\/?>(?:([^<]*)<\\/${tag}>)?`, "gi");
|
|
106
|
-
const results: string[] = [];
|
|
107
|
-
let match;
|
|
108
|
-
while ((match = regex.exec(xml)) !== null) {
|
|
109
|
-
// Only match exact tag name — skip tags like <DomainListResult> when searching for <Domain>
|
|
110
|
-
const fullMatch = match[0];
|
|
111
|
-
const tagNameCheck = new RegExp(`^<${tag}(?:\\s|\\/>|>)`, "i");
|
|
112
|
-
if (tagNameCheck.test(fullMatch)) {
|
|
113
|
-
results.push(fullMatch);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return results;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function extractAttributeFromElement(element: string, attr: string): string | null {
|
|
120
|
-
const regex = new RegExp(`${attr}="([^"]*)"`, "i");
|
|
121
|
-
const match = element.match(regex);
|
|
122
|
-
return match ? match[1] : null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function checkApiError(xml: string): void {
|
|
126
|
-
const status = extractAttribute(xml, "ApiResponse", "Status");
|
|
127
|
-
if (status === "ERROR") {
|
|
128
|
-
const errorMsg = extractTag(xml, "Message") || extractTag(xml, "Err") || "Unknown Namecheap API error";
|
|
129
|
-
const errorNumber = extractAttribute(xml, "Error", "Number") || extractAttribute(xml, "Err", "Number");
|
|
130
|
-
throw new Error(`Namecheap API error${errorNumber ? ` (${errorNumber})` : ""}: ${errorMsg}`);
|
|
131
|
-
}
|
|
67
|
+
function createConnector(config?: ConnectorConfig): Connector {
|
|
68
|
+
return new Connector(config || getConfig());
|
|
132
69
|
}
|
|
133
70
|
|
|
134
71
|
// ============================================================
|
|
135
|
-
//
|
|
136
|
-
// ============================================================
|
|
137
|
-
|
|
138
|
-
export function buildUrl(command: string, config: NamecheapConfig, extraParams?: Record<string, string>): string {
|
|
139
|
-
const base = config.sandbox ? SANDBOX_BASE : API_BASE;
|
|
140
|
-
const params = new URLSearchParams({
|
|
141
|
-
ApiUser: config.username,
|
|
142
|
-
ApiKey: config.apiKey,
|
|
143
|
-
UserName: config.username,
|
|
144
|
-
ClientIp: config.clientIp,
|
|
145
|
-
Command: command,
|
|
146
|
-
...extraParams,
|
|
147
|
-
});
|
|
148
|
-
return `${base}?${params.toString()}`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export async function apiRequest(command: string, config: NamecheapConfig, extraParams?: Record<string, string>): Promise<string> {
|
|
152
|
-
const url = buildUrl(command, config, extraParams);
|
|
153
|
-
const response = await fetch(url, {
|
|
154
|
-
signal: AbortSignal.timeout(30000),
|
|
155
|
-
headers: { "User-Agent": "microservice-domains/0.0.1" },
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
if (!response.ok) {
|
|
159
|
-
throw new Error(`Namecheap API HTTP error: ${response.status} ${response.statusText}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const xml = await response.text();
|
|
163
|
-
checkApiError(xml);
|
|
164
|
-
return xml;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// ============================================================
|
|
168
|
-
// API Functions
|
|
72
|
+
// API Functions (thin wrappers around connector)
|
|
169
73
|
// ============================================================
|
|
170
74
|
|
|
171
75
|
/**
|
|
172
76
|
* List all domains in the Namecheap account
|
|
173
|
-
* Command: namecheap.domains.getList
|
|
174
77
|
*/
|
|
175
|
-
export async function listNamecheapDomains(config?:
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
PageSize: "100",
|
|
179
|
-
Page: "1",
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const domainElements = extractAllTags(xml, "Domain");
|
|
183
|
-
const domains: NamecheapDomain[] = [];
|
|
184
|
-
|
|
185
|
-
for (const el of domainElements) {
|
|
186
|
-
const name = extractAttributeFromElement(el, "Name");
|
|
187
|
-
const expires = extractAttributeFromElement(el, "Expires");
|
|
188
|
-
const autoRenew = extractAttributeFromElement(el, "AutoRenew");
|
|
189
|
-
const isLocked = extractAttributeFromElement(el, "IsLocked");
|
|
190
|
-
|
|
191
|
-
if (name) {
|
|
192
|
-
domains.push({
|
|
193
|
-
domain: name,
|
|
194
|
-
expiry: expires || "",
|
|
195
|
-
autoRenew: autoRenew === "true",
|
|
196
|
-
isLocked: isLocked === "true",
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return domains;
|
|
78
|
+
export async function listNamecheapDomains(config?: ConnectorConfig): Promise<NamecheapDomain[]> {
|
|
79
|
+
const connector = createConnector(config);
|
|
80
|
+
return connector.domains.list();
|
|
202
81
|
}
|
|
203
82
|
|
|
204
83
|
/**
|
|
205
84
|
* Get detailed info for a specific domain
|
|
206
|
-
* Command: namecheap.domains.getInfo
|
|
207
85
|
*/
|
|
208
|
-
export async function getDomainInfo(domain: string, config?:
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
DomainName: domain,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Parse creation and expiry dates
|
|
215
|
-
const createdDate = extractTag(xml, "CreatedDate") || extractAttribute(xml, "DomainGetInfoResult", "CreatedDate") || "";
|
|
216
|
-
const expiresDate = extractTag(xml, "ExpiredDate") || extractAttribute(xml, "DomainGetInfoResult", "ExpiredDate") || "";
|
|
217
|
-
|
|
218
|
-
// Parse nameservers
|
|
219
|
-
const nsSection = xml.match(/<DnsDetails[^>]*>([\s\S]*?)<\/DnsDetails>/i);
|
|
220
|
-
const nameservers: string[] = [];
|
|
221
|
-
if (nsSection) {
|
|
222
|
-
const nsElements = nsSection[1].matchAll(/<Nameserver[^>]*>([^<]*)<\/Nameserver>/gi);
|
|
223
|
-
for (const m of nsElements) {
|
|
224
|
-
if (m[1]) nameservers.push(m[1].trim().toLowerCase());
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
domain,
|
|
230
|
-
registrar: "Namecheap",
|
|
231
|
-
created: createdDate,
|
|
232
|
-
expires: expiresDate,
|
|
233
|
-
nameservers,
|
|
234
|
-
};
|
|
86
|
+
export async function getDomainInfo(domain: string, config?: ConnectorConfig): Promise<NamecheapDomainInfo> {
|
|
87
|
+
const connector = createConnector(config);
|
|
88
|
+
return connector.domains.getInfo(domain);
|
|
235
89
|
}
|
|
236
90
|
|
|
237
91
|
/**
|
|
238
92
|
* Renew a domain
|
|
239
|
-
* Command: namecheap.domains.renew
|
|
240
93
|
*/
|
|
241
|
-
export async function renewDomain(domain: string, years: number = 1, config?:
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
DomainName: domain,
|
|
245
|
-
Years: String(years),
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const transactionId = extractAttribute(xml, "DomainRenewResult", "TransactionID") || undefined;
|
|
249
|
-
const chargedAmount = extractAttribute(xml, "DomainRenewResult", "ChargedAmount") || undefined;
|
|
250
|
-
const orderId = extractAttribute(xml, "DomainRenewResult", "OrderID") || undefined;
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
domain,
|
|
254
|
-
success: true,
|
|
255
|
-
transactionId,
|
|
256
|
-
chargedAmount,
|
|
257
|
-
orderId,
|
|
258
|
-
};
|
|
94
|
+
export async function renewDomain(domain: string, years: number = 1, config?: ConnectorConfig) {
|
|
95
|
+
const connector = createConnector(config);
|
|
96
|
+
return connector.domains.renew(domain, years);
|
|
259
97
|
}
|
|
260
98
|
|
|
261
99
|
/**
|
|
262
100
|
* Get DNS host records for a domain
|
|
263
|
-
* Command: namecheap.domains.dns.getHosts
|
|
264
101
|
*/
|
|
265
|
-
export async function getDnsRecords(domain: string, sld: string, tld: string, config?:
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
SLD: sld,
|
|
269
|
-
TLD: tld,
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
const hostElements = extractAllTags(xml, "host");
|
|
273
|
-
const records: NamecheapDnsRecord[] = [];
|
|
274
|
-
|
|
275
|
-
for (const el of hostElements) {
|
|
276
|
-
const type = extractAttributeFromElement(el, "Type");
|
|
277
|
-
const name = extractAttributeFromElement(el, "Name");
|
|
278
|
-
const address = extractAttributeFromElement(el, "Address");
|
|
279
|
-
const hostId = extractAttributeFromElement(el, "HostId");
|
|
280
|
-
const mxPref = extractAttributeFromElement(el, "MXPref");
|
|
281
|
-
const ttl = extractAttributeFromElement(el, "TTL");
|
|
282
|
-
|
|
283
|
-
if (type && name && address) {
|
|
284
|
-
records.push({
|
|
285
|
-
hostId: hostId || undefined,
|
|
286
|
-
type,
|
|
287
|
-
name,
|
|
288
|
-
address,
|
|
289
|
-
mxPref: mxPref ? parseInt(mxPref) : undefined,
|
|
290
|
-
ttl: ttl ? parseInt(ttl) : 1800,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return records;
|
|
102
|
+
export async function getDnsRecords(domain: string, sld: string, tld: string, config?: ConnectorConfig): Promise<NamecheapDnsRecord[]> {
|
|
103
|
+
const connector = createConnector(config);
|
|
104
|
+
return connector.dns.getHosts(sld, tld);
|
|
296
105
|
}
|
|
297
106
|
|
|
298
107
|
/**
|
|
299
108
|
* Set DNS host records for a domain
|
|
300
|
-
* Command: namecheap.domains.dns.setHosts
|
|
301
109
|
*/
|
|
302
110
|
export async function setDnsRecords(
|
|
303
111
|
domain: string,
|
|
304
112
|
sld: string,
|
|
305
113
|
tld: string,
|
|
306
114
|
records: NamecheapDnsRecord[],
|
|
307
|
-
config?:
|
|
115
|
+
config?: ConnectorConfig
|
|
308
116
|
): Promise<boolean> {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
SLD: sld,
|
|
312
|
-
TLD: tld,
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
for (let i = 0; i < records.length; i++) {
|
|
316
|
-
const r = records[i];
|
|
317
|
-
const idx = i + 1;
|
|
318
|
-
params[`HostName${idx}`] = r.name;
|
|
319
|
-
params[`RecordType${idx}`] = r.type;
|
|
320
|
-
params[`Address${idx}`] = r.address;
|
|
321
|
-
params[`TTL${idx}`] = String(r.ttl);
|
|
322
|
-
if (r.mxPref !== undefined) {
|
|
323
|
-
params[`MXPref${idx}`] = String(r.mxPref);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await apiRequest("namecheap.domains.dns.setHosts", cfg, params);
|
|
328
|
-
return true;
|
|
117
|
+
const connector = createConnector(config);
|
|
118
|
+
return connector.dns.setHosts(sld, tld, records);
|
|
329
119
|
}
|
|
330
120
|
|
|
331
121
|
/**
|
|
332
122
|
* Check domain availability
|
|
333
|
-
* Command: namecheap.domains.check
|
|
334
123
|
*/
|
|
335
|
-
export async function checkAvailability(domain: string, config?:
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
DomainList: domain,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
const available = extractAttribute(xml, "DomainCheckResult", "Available");
|
|
342
|
-
|
|
343
|
-
return {
|
|
344
|
-
domain,
|
|
345
|
-
available: available === "true",
|
|
346
|
-
};
|
|
124
|
+
export async function checkAvailability(domain: string, config?: ConnectorConfig) {
|
|
125
|
+
const connector = createConnector(config);
|
|
126
|
+
return connector.domains.check(domain);
|
|
347
127
|
}
|
|
348
128
|
|
|
129
|
+
// ============================================================
|
|
130
|
+
// Domain Helpers (microservice-specific)
|
|
131
|
+
// ============================================================
|
|
132
|
+
|
|
349
133
|
/**
|
|
350
134
|
* Split a domain name into SLD (second-level domain) and TLD (top-level domain)
|
|
351
135
|
*/
|
|
@@ -367,6 +151,10 @@ export function splitDomain(domain: string): { sld: string; tld: string } {
|
|
|
367
151
|
};
|
|
368
152
|
}
|
|
369
153
|
|
|
154
|
+
// ============================================================
|
|
155
|
+
// Sync to Local DB (microservice business logic)
|
|
156
|
+
// ============================================================
|
|
157
|
+
|
|
370
158
|
/**
|
|
371
159
|
* Sync domains from Namecheap to local database
|
|
372
160
|
* Calls listDomains + getDomainInfo for each, upserts into local domains table
|
|
@@ -393,23 +181,23 @@ export async function syncToLocalDb(dbFunctions: {
|
|
|
393
181
|
nameservers?: string[];
|
|
394
182
|
}
|
|
395
183
|
) => unknown;
|
|
396
|
-
}, config?:
|
|
397
|
-
const
|
|
184
|
+
}, config?: ConnectorConfig): Promise<NamecheapSyncResult> {
|
|
185
|
+
const connector = createConnector(config);
|
|
398
186
|
const result: NamecheapSyncResult = { synced: 0, errors: [], domains: [] };
|
|
399
187
|
|
|
400
188
|
let ncDomains: NamecheapDomain[];
|
|
401
189
|
try {
|
|
402
|
-
ncDomains = await
|
|
190
|
+
ncDomains = await connector.domains.list();
|
|
403
191
|
} catch (error) {
|
|
404
192
|
throw new Error(`Failed to list Namecheap domains: ${error instanceof Error ? error.message : String(error)}`);
|
|
405
193
|
}
|
|
406
194
|
|
|
407
195
|
for (const ncDomain of ncDomains) {
|
|
408
196
|
try {
|
|
409
|
-
// Get detailed info
|
|
197
|
+
// Get detailed info via connector
|
|
410
198
|
let info: NamecheapDomainInfo;
|
|
411
199
|
try {
|
|
412
|
-
info = await
|
|
200
|
+
info = await connector.domains.getInfo(ncDomain.domain);
|
|
413
201
|
} catch {
|
|
414
202
|
// Fall back to basic info if getInfo fails
|
|
415
203
|
info = {
|
package/package.json
CHANGED