tangerine 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +53 -26
  2. package/index.js +11 -6
  3. package/package.json +11 -3
package/README.md CHANGED
@@ -33,7 +33,7 @@
33
33
  * [ECMAScript modules (ESM)](#ecmascript-modules-esm)
34
34
  * [CommonJS (CJS)](#commonjs-cjs)
35
35
  * [API](#api)
36
- * [`new Tangerine(options)`](#new-tangerineoptions)
36
+ * [`new Tangerine(options[, request])`](#new-tangerineoptions-request)
37
37
  * [`tangerine.cancel()`](#tangerinecancel)
38
38
  * [`tangerine.getServers()`](#tangerinegetservers)
39
39
  * [`tangerine.lookup(hostname[, options])`](#tangerinelookuphostname-options)
@@ -66,7 +66,7 @@
66
66
  ## Install
67
67
 
68
68
  ```sh
69
- npm install tangerine
69
+ npm install tangerine undici
70
70
  ```
71
71
 
72
72
  ```diff
@@ -195,8 +195,36 @@ tangerine.resolve('forwardemail.net', 'A').then(console.log);
195
195
 
196
196
  ## API
197
197
 
198
- ### `new Tangerine(options)`
198
+ ### `new Tangerine(options[, request])`
199
199
 
200
+ * The `request` argument is a `Function` that defaults to [undici.request](https://undici.nodejs.org/#/?id=undicirequesturl-options-promise).
201
+ * This is an HTTP library request async or Promise returning function to be used for making requests.
202
+
203
+ * You could alternatively use [got](https://github.com/sindresorhus/got) or any other HTTP library of your choice that accepts `fn(url, options)`. However, we suggest to stick with the default of `undici` due to these [benchmark tests](http-library-benchmarks).
204
+
205
+ ```js
206
+ const tangerine = new Tangerine(
207
+ {
208
+ requestOptions: {
209
+ responseType: 'buffer',
210
+ decompress: false,
211
+ retry: {
212
+ limit: 0
213
+ }
214
+ },
215
+ requestTimeout: (ms) => ({ timeout: { request: ms } })
216
+ },
217
+ got
218
+ );
219
+ ```
220
+
221
+ * It should return an object with `body`, `headers`, and either a `status` or `statusCode` property.
222
+
223
+ * The `body` property returned should be either a `Buffer` or `Stream`.
224
+
225
+ * Specify default request options based off the library under `requestOptions` below
226
+
227
+ * See `requestTimeout` function below, as it is required to be set properly if you are using a custom HTTP library function.
200
228
  * Instance methods of [dns.promises.Resolver](https://nodejs.org/api/dns.html) are mirrored to :tangerine: Tangerine.
201
229
  * Resolver methods accept an optional `abortController` argument, which is an instance of [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). Note that :tangerine: Tangerine manages `AbortController` usage internally – so you most likely won't need to pass your own (see [index.js](https://github.com/forwardemail/tangerine/blob/main/index.js) for more insight).
202
230
  * See the complete list of [Options](#options) below.
@@ -251,29 +279,28 @@ Tangerine supports a new `ecsSubnet` property in the `options` Object argument.
251
279
 
252
280
  Similar to the `options` argument from `new dns.promises.Resolver(options)` invocation – :tangerine: Tangerine also has its own options with default `dns` behavior mirrored. See [index.js](https://github.com/forwardemail/tangerine/blob/main/index.js) for more insight into how these options work.
253
281
 
254
- | Property | Type | Default Value | Description |
255
- | ------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256
- | `timeout` | `Number` | `5000` | Number of milliseconds for requests to timeout. |
257
- | `tries` | `Number` | `4` | Number of tries per `server` in `servers` to attempt. |
258
- | `servers` | `Set` or `Array` | `new Set(['1.1.1.1', '1.0.0.1'])` | A Set or Array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted addresses for DNS queries (matches default Node.js dns module behavior). Duplicates will be removed as this is converted to a `Set` internally. Defaults to Cloudflare's of `1.1.1.1` and `1.0.0.1`. If an `Array` is passed, then it will be converted to a `Set`. |
259
- | `request` | `Function` | [undici.request](https://undici.nodejs.org/#/?id=undicirequesturl-options-promise) | HTTP library request async or Promise returning function to be used for making request (defaults to undici's request method). You could alternatively use `got` or any other HTTP library of your choice that accepts `fn(url, options)`. It should return an object with `body`, `headers`, and either a `status` or `statusCode` property. The `body` property returned should be either a `Buffer` or `Stream`. Specify default request options based off the library under `requestOptions` below (e.g. for `got` you'd set `responseType: 'buffer', retry: { limit: 0 }`) since this library already has built-in retries. See `requestTimeout` function below, as it is required to be set properly if you are using a custom HTTP library function. |
260
- | `requestOptions` | `Object` | Defaults to an Object with `requestOptions.method` and `requestOptions.headers` properties and values below | Default options to pass to [undici](https://github.com/nodejs/undici) (or your custom HTTP library function passed as `request`). |
261
- | `requestOptions.method` | `String` | Defaults to `"GET"` (must be either `"GET"` or `"POST"`, case-insensitive depending on library you use). | Default HTTP method to use for DNS over HTTP ("DoH") requests. |
262
- | `requestOptions.headers` | `Object` | Defaults to `{ 'content-type': 'application/dns-message', 'user-agent': pkg.name + "/" + pkg.version, accept: 'application/dns-message', bodyTimeout: timeout }`. | Default HTTP headers to use for DNS over HTTP ("DoH") requests. |
263
- | `requestTimeout` | `Function` | Defaults to `(ms) => ({ bodyTimeout })` for setting undici timeout properly. | This function accepts an argument `ms` which is the number of milliseconds to wait for the request to timeout (since we use a back-off strategy that mirrors the Node.js DNS module). This function is required to be passed and customized if you are using a custom HTTP library. If you're using a custom HTTP library such as `got`, you'd set this to `requestTimeout: (ms) => ({ timeout: { request: ms } })` |
264
- | `protocol` | `String` | Defaults to `"https"`. | Default HTTP protocol to use for DNS over HTTPS ("DoH") requests. |
265
- | `dnsOrder` | `String` | Defaults to `"verbatim"` for Node.js v17.0.0+ and `"ipv4first"` for older versions. | Sets the default result order of `lookup` invocations (see [dns.setDefaultResultOrder](https://nodejs.org/api/dns.html#dnssetdefaultresultorderorder) for more insight). |
266
- | `logger` | `Object` | `false` | This is the default logger. We recommend using [Cabin](https://github.com/cabinjs) instead of using `console` as your default logger. Set this value to `false` to disable logging entirely (uses noop function). |
267
- | `id` | `Number` or `Function` | `0` | Default `id` to be passed for DNS packet creation. This could alternatively be a synchronous or asynchronous function that returns a `Number` (e.g. `id: () => Tangerine.getRandomInt(1, 65534)`). |
268
- | `concurrency` | `Number` | `os.cpus().length` | Default concurrency to use for `resolveAny` lookup via [p-map](https://github.com/sindresorhus/p-map). The default value is the number of CPU's available to the system using the Node.js `os` module [os.cpus()](https://nodejs.org/api/os.html#oscpus) method. |
269
- | `ipv4` | `String` | `"0.0.0.0"` | Default IPv4 address to use for HTTP agent `localAddress` if DNS `server` was an IPv4 address. |
270
- | `ipv6` | `String` | `"::0"` | Default IPv6 address to use for HTTP agent `localAddress` if DNS `server` was an IPv6 address. |
271
- | `ipv4Port` | `Number` | `undefined` | Default port to use for HTTP agent `localPort` if DNS `server` was an IPv4 address. |
272
- | `ipv6Port` | `Number` | `undefined` | Default port to use for HTTP agent `localPort` if DNS `server` was an IPv6 address. |
273
- | `cache` | `Map` or `Boolean` | `new Map()` | Set this to `false` in order to disable caching. Default `Map` instance to use for caching. Entries are by type, e.g. `map.set('TXT', new Keyv({})`). If cache set values are not provided, then they will default to a new instance of `Keyv`. See cache setup and usage in [index.js](https://github.com/forwardemail/tangerine/blob/main/index.js) for more insight. You can iterate over `Tangerine.TYPES` if necessary to create a similar cache setup. |
274
- | `returnHTTPErrors` | `Boolean` | `false` | Whether to return HTTP errors instead of mapping them to corresponding DNS errors. |
275
- | `smartRotate` | `Boolean` | `true` | Whether to do smart server rotation if servers fail. |
276
- | `defaultHTTPErrorMessage` | `String` | `"Unsuccessful HTTP response"` | Default fallback message if `statusCode` returned from HTTP request was not found in [http.STATUS_CODES](https://nodejs.org/api/http.html#httpstatus_codes). |
282
+ | Property | Type | Default Value | Description |
283
+ | ------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
284
+ | `timeout` | `Number` | `5000` | Number of milliseconds for requests to timeout. |
285
+ | `tries` | `Number` | `4` | Number of tries per `server` in `servers` to attempt. |
286
+ | `servers` | `Set` or `Array` | `new Set(['1.1.1.1', '1.0.0.1'])` | A Set or Array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted addresses for DNS queries (matches default Node.js dns module behavior). Duplicates will be removed as this is converted to a `Set` internally. Defaults to Cloudflare's of `1.1.1.1` and `1.0.0.1`. If an `Array` is passed, then it will be converted to a `Set`. |
287
+ | `requestOptions` | `Object` | Defaults to an Object with `requestOptions.method` and `requestOptions.headers` properties and values below | Default options to pass to [undici](https://github.com/nodejs/undici) (or your custom HTTP library function passed as `request`). |
288
+ | `requestOptions.method` | `String` | Defaults to `"GET"` (must be either `"GET"` or `"POST"`, case-insensitive depending on library you use). | Default HTTP method to use for DNS over HTTP ("DoH") requests. |
289
+ | `requestOptions.headers` | `Object` | Defaults to `{ 'content-type': 'application/dns-message', 'user-agent': pkg.name + "/" + pkg.version, accept: 'application/dns-message', bodyTimeout: timeout }`. | Default HTTP headers to use for DNS over HTTP ("DoH") requests. |
290
+ | `requestTimeout` | `Function` | Defaults to `(ms) => ({ bodyTimeout })` for setting undici timeout properly. | This function accepts an argument `ms` which is the number of milliseconds to wait for the request to timeout (since we use a back-off strategy that mirrors the Node.js DNS module). This function is required to be passed and customized if you are using a custom HTTP library. If you're using a custom HTTP library such as `got`, you'd set this to `requestTimeout: (ms) => ({ timeout: { request: ms } })` |
291
+ | `protocol` | `String` | Defaults to `"https"`. | Default HTTP protocol to use for DNS over HTTPS ("DoH") requests. |
292
+ | `dnsOrder` | `String` | Defaults to `"verbatim"` for Node.js v17.0.0+ and `"ipv4first"` for older versions. | Sets the default result order of `lookup` invocations (see [dns.setDefaultResultOrder](https://nodejs.org/api/dns.html#dnssetdefaultresultorderorder) for more insight). |
293
+ | `logger` | `Object` | `false` | This is the default logger. We recommend using [Cabin](https://github.com/cabinjs) instead of using `console` as your default logger. Set this value to `false` to disable logging entirely (uses noop function). |
294
+ | `id` | `Number` or `Function` | `0` | Default `id` to be passed for DNS packet creation. This could alternatively be a synchronous or asynchronous function that returns a `Number` (e.g. `id: () => Tangerine.getRandomInt(1, 65534)`). |
295
+ | `concurrency` | `Number` | `os.cpus().length` | Default concurrency to use for `resolveAny` lookup via [p-map](https://github.com/sindresorhus/p-map). The default value is the number of CPU's available to the system using the Node.js `os` module [os.cpus()](https://nodejs.org/api/os.html#oscpus) method. |
296
+ | `ipv4` | `String` | `"0.0.0.0"` | Default IPv4 address to use for HTTP agent `localAddress` if DNS `server` was an IPv4 address. |
297
+ | `ipv6` | `String` | `"::0"` | Default IPv6 address to use for HTTP agent `localAddress` if DNS `server` was an IPv6 address. |
298
+ | `ipv4Port` | `Number` | `undefined` | Default port to use for HTTP agent `localPort` if DNS `server` was an IPv4 address. |
299
+ | `ipv6Port` | `Number` | `undefined` | Default port to use for HTTP agent `localPort` if DNS `server` was an IPv6 address. |
300
+ | `cache` | `Map` or `Boolean` | `new Map()` | Set this to `false` in order to disable caching. Default `Map` instance to use for caching. Entries are by type, e.g. `map.set('TXT', new Keyv({})`). If cache set values are not provided, then they will default to a new instance of `Keyv`. See cache setup and usage in [index.js](https://github.com/forwardemail/tangerine/blob/main/index.js) for more insight. You can iterate over `Tangerine.TYPES` if necessary to create a similar cache setup. |
301
+ | `returnHTTPErrors` | `Boolean` | `false` | Whether to return HTTP errors instead of mapping them to corresponding DNS errors. |
302
+ | `smartRotate` | `Boolean` | `true` | Whether to do smart server rotation if servers fail. |
303
+ | `defaultHTTPErrorMessage` | `String` | `"Unsuccessful HTTP response"` | Default fallback message if `statusCode` returned from HTTP request was not found in [http.STATUS_CODES](https://nodejs.org/api/http.html#httpstatus_codes). |
277
304
 
278
305
 
279
306
  ## Debugging
package/index.js CHANGED
@@ -16,7 +16,6 @@ const pWaitFor = require('p-wait-for');
16
16
  const packet = require('dns-packet');
17
17
  const semver = require('semver');
18
18
  const { getService } = require('port-numbers');
19
- const { request } = require('undici');
20
19
 
21
20
  const pkg = require('./package.json');
22
21
 
@@ -214,7 +213,7 @@ class Tangerine extends dns.promises.Resolver {
214
213
  return err;
215
214
  }
216
215
 
217
- constructor(options = {}) {
216
+ constructor(options = {}, request = require('undici').request) {
218
217
  const timeout =
219
218
  options.timeout && options.timeout !== -1 ? options.timeout : 5000;
220
219
  const tries = options.tries || 4;
@@ -224,6 +223,13 @@ class Tangerine extends dns.promises.Resolver {
224
223
  tries
225
224
  });
226
225
 
226
+ if (typeof request !== 'function')
227
+ throw new Error(
228
+ 'Request option must be a function (e.g. `undici.request` or `got`)'
229
+ );
230
+
231
+ this.request = request;
232
+
227
233
  this.options = mergeOptions(
228
234
  {
229
235
  // <https://github.com/nodejs/node/issues/33353#issuecomment-627259827>
@@ -237,8 +243,6 @@ class Tangerine extends dns.promises.Resolver {
237
243
  // dns servers will optionally retry in series
238
244
  // and servers that error will get shifted to the end of list
239
245
  servers: new Set(['1.1.1.1', '1.0.0.1']),
240
- // HTTP library function to use
241
- request,
242
246
  requestOptions: {
243
247
  method: 'GET',
244
248
  headers: {
@@ -768,7 +772,7 @@ class Tangerine extends dns.promises.Resolver {
768
772
  }
769
773
 
770
774
  debug('request', { url, options });
771
- const response = await this.options.request(url, options);
775
+ const response = await this.request(url, options);
772
776
  return response;
773
777
  }
774
778
 
@@ -811,7 +815,8 @@ class Tangerine extends dns.promises.Resolver {
811
815
  // if aborted signal then returns early
812
816
  // eslint-disable-next-line max-depth
813
817
  if (response) {
814
- const { statusCode, body, headers } = response;
818
+ const { body, headers } = response;
819
+ const statusCode = response.status || response.statusCode;
815
820
  debug('response', { statusCode, headers });
816
821
 
817
822
  // eslint-disable-next-line max-depth
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tangerine",
3
3
  "description": "Tangerine is the best Node.js drop-in replacement for dns.promises.Resolver using DNS over HTTPS (\"DoH\") via undici with built-in retries, timeouts, smart server rotation, AbortControllers, and caching support for multiple backends via Keyv.",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "author": "Forward Email (https://forwardemail.net)",
6
6
  "bugs": {
7
7
  "url": "https://github.com/forwardemail/tangerine/issues"
@@ -19,8 +19,7 @@
19
19
  "p-map": "4",
20
20
  "p-wait-for": "3",
21
21
  "port-numbers": "^6.0.1",
22
- "semver": "^7.3.8",
23
- "undici": "^5.20.0"
22
+ "semver": "^7.3.8"
24
23
  },
25
24
  "devDependencies": {
26
25
  "@commitlint/cli": "^17.4.4",
@@ -46,6 +45,7 @@
46
45
  "request": "^2.88.2",
47
46
  "sort-keys": "4.2.0",
48
47
  "superagent": "^8.0.9",
48
+ "undici": "^5.20.0",
49
49
  "xo": "^0.53.1"
50
50
  },
51
51
  "engines": {
@@ -132,6 +132,14 @@
132
132
  ],
133
133
  "license": "MIT",
134
134
  "main": "index.js",
135
+ "peerDependencies": {
136
+ "undici": "*"
137
+ },
138
+ "peerDependenciesMeta": {
139
+ "undici": {
140
+ "optional": true
141
+ }
142
+ },
135
143
  "publishConfig": {
136
144
  "access": "public"
137
145
  },