@gjsify/dns 0.1.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 +29 -0
- package/lib/esm/index.js +263 -0
- package/lib/esm/promises.js +61 -0
- package/lib/types/index.d.ts +90 -0
- package/lib/types/promises.d.ts +14 -0
- package/package.json +48 -0
- package/src/index.spec.ts +396 -0
- package/src/index.ts +314 -0
- package/src/promises.spec.ts +125 -0
- package/src/promises.ts +63 -0
- package/src/test.mts +7 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { describe, it, expect } from '@gjsify/unit';
|
|
2
|
+
import dns, {
|
|
3
|
+
lookup,
|
|
4
|
+
resolve4,
|
|
5
|
+
resolve6,
|
|
6
|
+
reverse,
|
|
7
|
+
resolve,
|
|
8
|
+
setDefaultResultOrder,
|
|
9
|
+
getDefaultResultOrder,
|
|
10
|
+
NODATA,
|
|
11
|
+
FORMERR,
|
|
12
|
+
SERVFAIL,
|
|
13
|
+
NOTFOUND,
|
|
14
|
+
NOTIMP,
|
|
15
|
+
REFUSED,
|
|
16
|
+
BADQUERY,
|
|
17
|
+
BADNAME,
|
|
18
|
+
BADFAMILY,
|
|
19
|
+
BADRESP,
|
|
20
|
+
CONNREFUSED,
|
|
21
|
+
TIMEOUT,
|
|
22
|
+
EOF,
|
|
23
|
+
FILE,
|
|
24
|
+
NOMEM,
|
|
25
|
+
DESTRUCTION,
|
|
26
|
+
BADSTR,
|
|
27
|
+
BADFLAGS,
|
|
28
|
+
NONAME,
|
|
29
|
+
BADHINTS,
|
|
30
|
+
NOTINITIALIZED,
|
|
31
|
+
CANCELLED,
|
|
32
|
+
} from 'node:dns';
|
|
33
|
+
|
|
34
|
+
export default async () => {
|
|
35
|
+
await describe('dns', async () => {
|
|
36
|
+
|
|
37
|
+
// --- Constants ---
|
|
38
|
+
await describe('constants', async () => {
|
|
39
|
+
await it('should export NOTFOUND as ENOTFOUND', async () => {
|
|
40
|
+
expect(NOTFOUND).toBe('ENOTFOUND');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await it('should export NODATA as ENODATA', async () => {
|
|
44
|
+
expect(NODATA).toBe('ENODATA');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await it('should export SERVFAIL as ESERVFAIL', async () => {
|
|
48
|
+
expect(SERVFAIL).toBe('ESERVFAIL');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await it('should export FORMERR as EFORMERR', async () => {
|
|
52
|
+
expect(FORMERR).toBe('EFORMERR');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await it('should export NOTIMP as ENOTIMP', async () => {
|
|
56
|
+
expect(NOTIMP).toBe('ENOTIMP');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await it('should export REFUSED as EREFUSED', async () => {
|
|
60
|
+
expect(REFUSED).toBe('EREFUSED');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await it('should export BADQUERY as EBADQUERY', async () => {
|
|
64
|
+
expect(BADQUERY).toBe('EBADQUERY');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await it('should export CONNREFUSED as ECONNREFUSED', async () => {
|
|
68
|
+
expect(CONNREFUSED).toBe('ECONNREFUSED');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await it('should export TIMEOUT as ETIMEOUT', async () => {
|
|
72
|
+
expect(TIMEOUT).toBe('ETIMEOUT');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await it('should export remaining error codes', async () => {
|
|
76
|
+
expect(BADNAME).toBe('EBADNAME');
|
|
77
|
+
expect(BADFAMILY).toBe('EBADFAMILY');
|
|
78
|
+
expect(BADRESP).toBe('EBADRESP');
|
|
79
|
+
expect(EOF).toBe('EOF');
|
|
80
|
+
expect(FILE).toBe('EFILE');
|
|
81
|
+
expect(NOMEM).toBe('ENOMEM');
|
|
82
|
+
expect(DESTRUCTION).toBe('EDESTRUCTION');
|
|
83
|
+
expect(BADSTR).toBe('EBADSTR');
|
|
84
|
+
expect(BADFLAGS).toBe('EBADFLAGS');
|
|
85
|
+
expect(NONAME).toBe('ENONAME');
|
|
86
|
+
expect(BADHINTS).toBe('EBADHINTS');
|
|
87
|
+
expect(NOTINITIALIZED).toBe('ENOTINITIALIZED');
|
|
88
|
+
expect(CANCELLED).toBe('ECANCELLED');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// --- Module exports ---
|
|
93
|
+
await describe('exports', async () => {
|
|
94
|
+
await it('should export lookup as a function', async () => {
|
|
95
|
+
expect(typeof lookup).toBe('function');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await it('should export resolve as a function', async () => {
|
|
99
|
+
expect(typeof resolve).toBe('function');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await it('should export resolve4 as a function', async () => {
|
|
103
|
+
expect(typeof resolve4).toBe('function');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await it('should export resolve6 as a function', async () => {
|
|
107
|
+
expect(typeof resolve6).toBe('function');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await it('should export reverse as a function', async () => {
|
|
111
|
+
expect(typeof reverse).toBe('function');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await it('should export setDefaultResultOrder and getDefaultResultOrder', async () => {
|
|
115
|
+
expect(typeof setDefaultResultOrder).toBe('function');
|
|
116
|
+
expect(typeof getDefaultResultOrder).toBe('function');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await it('should have all exports on the default export object', async () => {
|
|
120
|
+
expect(typeof dns.lookup).toBe('function');
|
|
121
|
+
expect(typeof dns.resolve).toBe('function');
|
|
122
|
+
expect(typeof dns.resolve4).toBe('function');
|
|
123
|
+
expect(typeof dns.resolve6).toBe('function');
|
|
124
|
+
expect(typeof dns.reverse).toBe('function');
|
|
125
|
+
expect(typeof dns.setDefaultResultOrder).toBe('function');
|
|
126
|
+
expect(typeof dns.getDefaultResultOrder).toBe('function');
|
|
127
|
+
expect(dns.NOTFOUND).toBe('ENOTFOUND');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// --- lookup ---
|
|
132
|
+
await describe('lookup', async () => {
|
|
133
|
+
await it('should resolve localhost', async () => {
|
|
134
|
+
await new Promise<void>((resolve, reject) => {
|
|
135
|
+
lookup('localhost', (err, address, family) => {
|
|
136
|
+
if (err) return reject(err);
|
|
137
|
+
expect(address).toBeDefined();
|
|
138
|
+
expect(typeof address).toBe('string');
|
|
139
|
+
expect(family === 4 || family === 6).toBe(true);
|
|
140
|
+
resolve();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await it('should return IPv4 address unchanged', async () => {
|
|
146
|
+
await new Promise<void>((resolve) => {
|
|
147
|
+
lookup('127.0.0.1', (err, address, family) => {
|
|
148
|
+
expect(err).toBeNull();
|
|
149
|
+
expect(address).toBe('127.0.0.1');
|
|
150
|
+
expect(family).toBe(4);
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await it('should return IPv6 address unchanged', async () => {
|
|
157
|
+
await new Promise<void>((resolve) => {
|
|
158
|
+
lookup('::1', (err, address, family) => {
|
|
159
|
+
expect(err).toBeNull();
|
|
160
|
+
expect(address).toBe('::1');
|
|
161
|
+
expect(family).toBe(6);
|
|
162
|
+
resolve();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await it('should accept family option as number', async () => {
|
|
168
|
+
await new Promise<void>((resolve) => {
|
|
169
|
+
lookup('127.0.0.1', 4, (err, address, family) => {
|
|
170
|
+
expect(err).toBeNull();
|
|
171
|
+
expect(address).toBe('127.0.0.1');
|
|
172
|
+
expect(family).toBe(4);
|
|
173
|
+
resolve();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await it('should accept family option in options object', async () => {
|
|
179
|
+
await new Promise<void>((resolve) => {
|
|
180
|
+
lookup('127.0.0.1', { family: 4 }, (err, address, family) => {
|
|
181
|
+
expect(err).toBeNull();
|
|
182
|
+
expect(address).toBe('127.0.0.1');
|
|
183
|
+
expect(family).toBe(4);
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await it('should return null for empty hostname', async () => {
|
|
190
|
+
await new Promise<void>((resolve) => {
|
|
191
|
+
lookup('', (err, address, family) => {
|
|
192
|
+
expect(err).toBeNull();
|
|
193
|
+
expect(address).toBeNull();
|
|
194
|
+
expect(family).toBe(4);
|
|
195
|
+
resolve();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await it('should return null for empty hostname with family 6', async () => {
|
|
201
|
+
await new Promise<void>((resolve) => {
|
|
202
|
+
lookup('', { family: 6 }, (err, address, family) => {
|
|
203
|
+
expect(err).toBeNull();
|
|
204
|
+
expect(address).toBeNull();
|
|
205
|
+
expect(family).toBe(6);
|
|
206
|
+
resolve();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await it('should return all addresses with all: true', async () => {
|
|
212
|
+
await new Promise<void>((resolve) => {
|
|
213
|
+
lookup('localhost', { all: true }, (err, addresses: any) => {
|
|
214
|
+
expect(err).toBeNull();
|
|
215
|
+
expect(Array.isArray(addresses)).toBe(true);
|
|
216
|
+
expect(addresses.length).toBeGreaterThan(0);
|
|
217
|
+
for (const entry of addresses) {
|
|
218
|
+
expect(typeof entry.address).toBe('string');
|
|
219
|
+
expect(entry.family === 4 || entry.family === 6).toBe(true);
|
|
220
|
+
}
|
|
221
|
+
resolve();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await it('should error on non-existent hostname', async () => {
|
|
227
|
+
await new Promise<void>((resolve) => {
|
|
228
|
+
lookup('this.hostname.does.not.exist.invalid', (err, address, family) => {
|
|
229
|
+
expect(err).toBeDefined();
|
|
230
|
+
expect((err as any).code).toBe('ENOTFOUND');
|
|
231
|
+
resolve();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// --- resolve4 / resolve6 ---
|
|
238
|
+
await describe('resolve4', async () => {
|
|
239
|
+
await it('should resolve localhost to IPv4 addresses', async () => {
|
|
240
|
+
await new Promise<void>((resolve, reject) => {
|
|
241
|
+
resolve4('localhost', (err, addresses) => {
|
|
242
|
+
if (err) return reject(err);
|
|
243
|
+
expect(Array.isArray(addresses)).toBe(true);
|
|
244
|
+
expect(addresses.length).toBeGreaterThan(0);
|
|
245
|
+
for (const addr of addresses) {
|
|
246
|
+
expect(typeof addr).toBe('string');
|
|
247
|
+
}
|
|
248
|
+
resolve();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await describe('resolve', async () => {
|
|
255
|
+
await it('should resolve with default rrtype (A record)', async () => {
|
|
256
|
+
await new Promise<void>((resolve, reject) => {
|
|
257
|
+
resolve('localhost', (err, records) => {
|
|
258
|
+
if (err) return reject(err);
|
|
259
|
+
expect(Array.isArray(records)).toBe(true);
|
|
260
|
+
resolve();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
await it('should resolve with explicit A rrtype', async () => {
|
|
266
|
+
await new Promise<void>((res, reject) => {
|
|
267
|
+
resolve('localhost', 'A', (err, records) => {
|
|
268
|
+
if (err) return reject(err);
|
|
269
|
+
expect(Array.isArray(records)).toBe(true);
|
|
270
|
+
res();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// --- reverse ---
|
|
277
|
+
await describe('reverse', async () => {
|
|
278
|
+
await it('should reverse lookup 127.0.0.1', async () => {
|
|
279
|
+
await new Promise<void>((resolve) => {
|
|
280
|
+
reverse('127.0.0.1', (err, hostnames) => {
|
|
281
|
+
// May succeed or fail depending on system config
|
|
282
|
+
if (err) {
|
|
283
|
+
expect(typeof (err as any).code).toBe('string');
|
|
284
|
+
} else {
|
|
285
|
+
expect(Array.isArray(hostnames)).toBe(true);
|
|
286
|
+
}
|
|
287
|
+
resolve();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// --- setDefaultResultOrder / getDefaultResultOrder ---
|
|
294
|
+
await describe('setDefaultResultOrder', async () => {
|
|
295
|
+
await it('should return verbatim by default', async () => {
|
|
296
|
+
expect(getDefaultResultOrder()).toBe('verbatim');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
await it('should set to ipv4first', async () => {
|
|
300
|
+
setDefaultResultOrder('ipv4first');
|
|
301
|
+
expect(getDefaultResultOrder()).toBe('ipv4first');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
await it('should set back to verbatim', async () => {
|
|
305
|
+
setDefaultResultOrder('verbatim');
|
|
306
|
+
expect(getDefaultResultOrder()).toBe('verbatim');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// --- Additional export checks ---
|
|
311
|
+
await describe('additional exports', async () => {
|
|
312
|
+
await it('resolve6 should be a function', async () => {
|
|
313
|
+
expect(typeof resolve6).toBe('function');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await it('resolve6 should resolve localhost', async () => {
|
|
317
|
+
await new Promise<void>((resolve) => {
|
|
318
|
+
resolve6('localhost', (err, addresses) => {
|
|
319
|
+
// May fail if no IPv6 configured, but should not crash
|
|
320
|
+
if (err) {
|
|
321
|
+
expect(typeof (err as any).code).toBe('string');
|
|
322
|
+
} else {
|
|
323
|
+
expect(Array.isArray(addresses)).toBe(true);
|
|
324
|
+
expect(addresses.length).toBeGreaterThan(0);
|
|
325
|
+
for (const addr of addresses) {
|
|
326
|
+
expect(typeof addr).toBe('string');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
resolve();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await it('dns.getServers should be a function if exported', async () => {
|
|
335
|
+
// getServers may or may not be implemented
|
|
336
|
+
expect(typeof dns.getServers === 'function' || typeof dns.getServers === 'undefined').toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await it('dns.setServers should be a function if exported', async () => {
|
|
340
|
+
// setServers may or may not be implemented
|
|
341
|
+
expect(typeof dns.setServers === 'function' || typeof dns.setServers === 'undefined').toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await it('dns.Resolver should be a constructor if exported', async () => {
|
|
345
|
+
// Resolver class may or may not be implemented
|
|
346
|
+
expect(typeof dns.Resolver === 'function' || typeof dns.Resolver === 'undefined').toBe(true);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// --- lookup with hints option ---
|
|
351
|
+
await describe('lookup with hints', async () => {
|
|
352
|
+
await it('lookup with hints option should not throw', async () => {
|
|
353
|
+
await new Promise<void>((resolve) => {
|
|
354
|
+
lookup('127.0.0.1', { hints: 0 }, (err, address, family) => {
|
|
355
|
+
expect(err).toBeNull();
|
|
356
|
+
expect(address).toBe('127.0.0.1');
|
|
357
|
+
expect(family).toBe(4);
|
|
358
|
+
resolve();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// --- resolve with different rrtypes ---
|
|
365
|
+
await describe('resolve with rrtypes', async () => {
|
|
366
|
+
await it('resolve with MX rrtype should be supported', async () => {
|
|
367
|
+
// We just verify the function accepts the rrtype without crashing
|
|
368
|
+
await new Promise<void>((resolve) => {
|
|
369
|
+
dns.resolve('localhost', 'MX', (err: any, records: any) => {
|
|
370
|
+
// MX for localhost may return error (no MX records), that's fine
|
|
371
|
+
if (err) {
|
|
372
|
+
expect(typeof err.code === 'string' || typeof err.message === 'string').toBe(true);
|
|
373
|
+
} else {
|
|
374
|
+
expect(Array.isArray(records)).toBe(true);
|
|
375
|
+
}
|
|
376
|
+
resolve();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
await it('resolve with TXT rrtype should be supported', async () => {
|
|
382
|
+
await new Promise<void>((resolve) => {
|
|
383
|
+
dns.resolve('localhost', 'TXT', (err: any, records: any) => {
|
|
384
|
+
// TXT for localhost may return error (no TXT records), that's fine
|
|
385
|
+
if (err) {
|
|
386
|
+
expect(typeof err.code === 'string' || typeof err.message === 'string').toBe(true);
|
|
387
|
+
} else {
|
|
388
|
+
expect(Array.isArray(records)).toBe(true);
|
|
389
|
+
}
|
|
390
|
+
resolve();
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// Reference: Node.js lib/dns.js
|
|
2
|
+
// Reimplemented for GJS using Gio.Resolver
|
|
3
|
+
|
|
4
|
+
import Gio from '@girs/gio-2.0';
|
|
5
|
+
import GLib from '@girs/glib-2.0';
|
|
6
|
+
import { createNodeError } from '@gjsify/utils';
|
|
7
|
+
import type { ErrnoException } from '@gjsify/utils';
|
|
8
|
+
import { isIP } from 'node:net';
|
|
9
|
+
|
|
10
|
+
// Error codes
|
|
11
|
+
export const NODATA = 'ENODATA';
|
|
12
|
+
export const FORMERR = 'EFORMERR';
|
|
13
|
+
export const SERVFAIL = 'ESERVFAIL';
|
|
14
|
+
export const NOTFOUND = 'ENOTFOUND';
|
|
15
|
+
export const NOTIMP = 'ENOTIMP';
|
|
16
|
+
export const REFUSED = 'EREFUSED';
|
|
17
|
+
export const BADQUERY = 'EBADQUERY';
|
|
18
|
+
export const BADNAME = 'EBADNAME';
|
|
19
|
+
export const BADFAMILY = 'EBADFAMILY';
|
|
20
|
+
export const BADRESP = 'EBADRESP';
|
|
21
|
+
export const CONNREFUSED = 'ECONNREFUSED';
|
|
22
|
+
export const TIMEOUT = 'ETIMEOUT';
|
|
23
|
+
export const EOF = 'EOF';
|
|
24
|
+
export const FILE = 'EFILE';
|
|
25
|
+
export const NOMEM = 'ENOMEM';
|
|
26
|
+
export const DESTRUCTION = 'EDESTRUCTION';
|
|
27
|
+
export const BADSTR = 'EBADSTR';
|
|
28
|
+
export const BADFLAGS = 'EBADFLAGS';
|
|
29
|
+
export const NONAME = 'ENONAME';
|
|
30
|
+
export const BADHINTS = 'EBADHINTS';
|
|
31
|
+
export const NOTINITIALIZED = 'ENOTINITIALIZED';
|
|
32
|
+
export const LOADIPHLPAPI = 'ELOADIPHLPAPI';
|
|
33
|
+
export const ADDRGETNETWORKPARAMS = 'EADDRGETNETWORKPARAMS';
|
|
34
|
+
export const CANCELLED = 'ECANCELLED';
|
|
35
|
+
|
|
36
|
+
export interface LookupOptions {
|
|
37
|
+
family?: 0 | 4 | 6;
|
|
38
|
+
hints?: number;
|
|
39
|
+
all?: boolean;
|
|
40
|
+
verbatim?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface LookupAddress {
|
|
44
|
+
address: string;
|
|
45
|
+
family: 4 | 6;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createDnsError(hostname: string, syscall: string, _err?: unknown): ErrnoException {
|
|
49
|
+
const code = NOTFOUND;
|
|
50
|
+
const message = `${syscall} ${code} ${hostname}`;
|
|
51
|
+
const error = new Error(message) as ErrnoException;
|
|
52
|
+
error.code = code;
|
|
53
|
+
error.syscall = syscall;
|
|
54
|
+
error.hostname = hostname;
|
|
55
|
+
error.errno = undefined;
|
|
56
|
+
return error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a hostname to an IP address using the system DNS resolver.
|
|
61
|
+
*/
|
|
62
|
+
export function lookup(
|
|
63
|
+
hostname: string,
|
|
64
|
+
options: LookupOptions | number | null | undefined,
|
|
65
|
+
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void,
|
|
66
|
+
): void;
|
|
67
|
+
export function lookup(
|
|
68
|
+
hostname: string,
|
|
69
|
+
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void,
|
|
70
|
+
): void;
|
|
71
|
+
export function lookup(
|
|
72
|
+
hostname: string,
|
|
73
|
+
...args: [LookupOptions | number | null | undefined, Function] | [Function]
|
|
74
|
+
): void {
|
|
75
|
+
let options: LookupOptions = {};
|
|
76
|
+
let callback: Function;
|
|
77
|
+
|
|
78
|
+
if (typeof args[0] === 'function') {
|
|
79
|
+
callback = args[0];
|
|
80
|
+
} else {
|
|
81
|
+
if (typeof args[0] === 'number') {
|
|
82
|
+
options = { family: args[0] as 0 | 4 | 6 };
|
|
83
|
+
} else if (args[0]) {
|
|
84
|
+
options = args[0];
|
|
85
|
+
}
|
|
86
|
+
callback = args[1];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle empty hostname — Node.js returns null for address
|
|
90
|
+
if (!hostname) {
|
|
91
|
+
const family = options.family || 4;
|
|
92
|
+
callback(null, null as unknown as string, family);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle IP addresses directly (no DNS needed)
|
|
97
|
+
const ipVersion = isIP(hostname);
|
|
98
|
+
if (ipVersion) {
|
|
99
|
+
callback(null, hostname, ipVersion);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const resolver = Gio.Resolver.get_default();
|
|
104
|
+
|
|
105
|
+
resolver.lookup_by_name_async(
|
|
106
|
+
hostname,
|
|
107
|
+
null, // cancellable
|
|
108
|
+
(_source: Gio.Resolver | null, asyncResult: Gio.AsyncResult) => {
|
|
109
|
+
try {
|
|
110
|
+
const addresses = resolver.lookup_by_name_finish(asyncResult);
|
|
111
|
+
|
|
112
|
+
if (!addresses || addresses.length === 0) {
|
|
113
|
+
callback(createDnsError(hostname, 'getaddrinfo'), '', 0);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Filter by family if requested
|
|
118
|
+
const family = options.family || 0;
|
|
119
|
+
let results: Gio.InetAddress[] = [];
|
|
120
|
+
|
|
121
|
+
for (const addr of addresses) {
|
|
122
|
+
const addrFamily = addr.get_family();
|
|
123
|
+
if (family === 0 ||
|
|
124
|
+
(family === 4 && addrFamily === Gio.SocketFamily.IPV4) ||
|
|
125
|
+
(family === 6 && addrFamily === Gio.SocketFamily.IPV6)) {
|
|
126
|
+
results.push(addr);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (results.length === 0) {
|
|
131
|
+
// Fall back to any available if no match for requested family
|
|
132
|
+
results = Array.from(addresses);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (options.all) {
|
|
136
|
+
const allAddresses: LookupAddress[] = results.map((addr) => ({
|
|
137
|
+
address: addr.to_string(),
|
|
138
|
+
family: addr.get_family() === Gio.SocketFamily.IPV6 ? 6 as const : 4 as const,
|
|
139
|
+
}));
|
|
140
|
+
(callback as any)(null, allAddresses);
|
|
141
|
+
} else {
|
|
142
|
+
const first = results[0];
|
|
143
|
+
const addrStr = first.to_string();
|
|
144
|
+
const addrFamily = first.get_family() === Gio.SocketFamily.IPV6 ? 6 : 4;
|
|
145
|
+
callback(null, addrStr, addrFamily);
|
|
146
|
+
}
|
|
147
|
+
} catch (_err: unknown) {
|
|
148
|
+
callback(createDnsError(hostname, 'getaddrinfo'), '', 0);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolve a hostname to an array of IPv4 addresses.
|
|
156
|
+
*/
|
|
157
|
+
export function resolve4(hostname: string, callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void): void {
|
|
158
|
+
lookup(hostname, { family: 4, all: true }, (err, addresses) => {
|
|
159
|
+
if (err) {
|
|
160
|
+
callback(err, []);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
callback(null, (addresses as unknown as LookupAddress[]).map((a) => a.address));
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a hostname to an array of IPv6 addresses.
|
|
169
|
+
*/
|
|
170
|
+
export function resolve6(hostname: string, callback: (err: NodeJS.ErrnoException | null, addresses: string[]) => void): void {
|
|
171
|
+
lookup(hostname, { family: 6, all: true }, (err, addresses) => {
|
|
172
|
+
if (err) {
|
|
173
|
+
callback(err, []);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
callback(null, (addresses as unknown as LookupAddress[]).map((a) => a.address));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Reverse DNS lookup.
|
|
182
|
+
*/
|
|
183
|
+
export function reverse(ip: string, callback: (err: NodeJS.ErrnoException | null, hostnames: string[]) => void): void {
|
|
184
|
+
const resolver = Gio.Resolver.get_default();
|
|
185
|
+
const addr = Gio.InetAddress.new_from_string(ip);
|
|
186
|
+
|
|
187
|
+
if (!addr) {
|
|
188
|
+
const err = new Error(`Invalid IP address: ${ip}`) as NodeJS.ErrnoException;
|
|
189
|
+
err.code = 'ERR_INVALID_ARG_VALUE';
|
|
190
|
+
callback(err, []);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
resolver.lookup_by_address_async(
|
|
195
|
+
addr,
|
|
196
|
+
null,
|
|
197
|
+
(_source: Gio.Resolver | null, asyncResult: Gio.AsyncResult) => {
|
|
198
|
+
try {
|
|
199
|
+
const hostname = resolver.lookup_by_address_finish(asyncResult);
|
|
200
|
+
callback(null, hostname ? [hostname] : []);
|
|
201
|
+
} catch (_err: unknown) {
|
|
202
|
+
callback(createDnsError(ip, 'getnameinfo'), []);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve hostname using specified record type.
|
|
210
|
+
*/
|
|
211
|
+
export function resolve(hostname: string, rrtype: string, callback: (err: NodeJS.ErrnoException | null, records: unknown[]) => void): void;
|
|
212
|
+
export function resolve(hostname: string, callback: (err: NodeJS.ErrnoException | null, records: string[]) => void): void;
|
|
213
|
+
export function resolve(hostname: string, ...args: [string, Function] | [Function]): void {
|
|
214
|
+
let rrtype = 'A';
|
|
215
|
+
let callback: Function;
|
|
216
|
+
|
|
217
|
+
if (typeof args[0] === 'function') {
|
|
218
|
+
callback = args[0];
|
|
219
|
+
} else {
|
|
220
|
+
rrtype = args[0];
|
|
221
|
+
callback = args[1];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
switch (rrtype.toUpperCase()) {
|
|
225
|
+
case 'A':
|
|
226
|
+
resolve4(hostname, callback as (err: NodeJS.ErrnoException | null, addresses: string[]) => void);
|
|
227
|
+
break;
|
|
228
|
+
case 'AAAA':
|
|
229
|
+
resolve6(hostname, callback as (err: NodeJS.ErrnoException | null, addresses: string[]) => void);
|
|
230
|
+
break;
|
|
231
|
+
default:
|
|
232
|
+
// For other record types (MX, TXT, SRV, etc.), use Gio.Resolver.lookup_records
|
|
233
|
+
_resolveRecords(hostname, rrtype, callback);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function _resolveRecords(hostname: string, rrtype: string, callback: Function): void {
|
|
238
|
+
const resolver = Gio.Resolver.get_default();
|
|
239
|
+
|
|
240
|
+
const recordTypeMap: Record<string, Gio.ResolverRecordType> = {
|
|
241
|
+
SRV: Gio.ResolverRecordType.SRV,
|
|
242
|
+
MX: Gio.ResolverRecordType.MX,
|
|
243
|
+
TXT: Gio.ResolverRecordType.TXT,
|
|
244
|
+
SOA: Gio.ResolverRecordType.SOA,
|
|
245
|
+
NS: Gio.ResolverRecordType.NS,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const gioType = recordTypeMap[rrtype.toUpperCase()];
|
|
249
|
+
if (!gioType) {
|
|
250
|
+
const err = new Error(`Unknown record type: ${rrtype}`) as NodeJS.ErrnoException;
|
|
251
|
+
err.code = 'ERR_INVALID_ARG_VALUE';
|
|
252
|
+
callback(err, []);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
resolver.lookup_records_async(
|
|
257
|
+
hostname,
|
|
258
|
+
gioType,
|
|
259
|
+
null,
|
|
260
|
+
(_source: Gio.Resolver | null, asyncResult: Gio.AsyncResult) => {
|
|
261
|
+
try {
|
|
262
|
+
const records = resolver.lookup_records_finish(asyncResult);
|
|
263
|
+
// Records are GLib.Variant arrays — convert to JS
|
|
264
|
+
const results = records.map((r: GLib.Variant) => r.deep_unpack());
|
|
265
|
+
callback(null, results as unknown[]);
|
|
266
|
+
} catch (_err: unknown) {
|
|
267
|
+
callback(createDnsError(hostname, 'queryDns'), []);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Default result order
|
|
274
|
+
let _defaultResultOrder: 'ipv4first' | 'verbatim' = 'verbatim';
|
|
275
|
+
|
|
276
|
+
export function setDefaultResultOrder(order: 'ipv4first' | 'verbatim'): void {
|
|
277
|
+
_defaultResultOrder = order;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function getDefaultResultOrder(): string {
|
|
281
|
+
return _defaultResultOrder;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default {
|
|
285
|
+
lookup,
|
|
286
|
+
resolve,
|
|
287
|
+
resolve4,
|
|
288
|
+
resolve6,
|
|
289
|
+
reverse,
|
|
290
|
+
setDefaultResultOrder,
|
|
291
|
+
getDefaultResultOrder,
|
|
292
|
+
NODATA,
|
|
293
|
+
FORMERR,
|
|
294
|
+
SERVFAIL,
|
|
295
|
+
NOTFOUND,
|
|
296
|
+
NOTIMP,
|
|
297
|
+
REFUSED,
|
|
298
|
+
BADQUERY,
|
|
299
|
+
BADNAME,
|
|
300
|
+
BADFAMILY,
|
|
301
|
+
BADRESP,
|
|
302
|
+
CONNREFUSED,
|
|
303
|
+
TIMEOUT,
|
|
304
|
+
EOF,
|
|
305
|
+
FILE,
|
|
306
|
+
NOMEM,
|
|
307
|
+
DESTRUCTION,
|
|
308
|
+
BADSTR,
|
|
309
|
+
BADFLAGS,
|
|
310
|
+
NONAME,
|
|
311
|
+
BADHINTS,
|
|
312
|
+
NOTINITIALIZED,
|
|
313
|
+
CANCELLED,
|
|
314
|
+
};
|