tangerine 1.5.3 → 1.5.5
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 +3 -12
- package/index.js +30 -4
- package/package.json +24 -23
package/README.md
CHANGED
|
@@ -89,8 +89,7 @@ npm install tangerine undici
|
|
|
89
89
|
|
|
90
90
|
Our team at [Forward Email](https://forwardemail.net) (100% open-source and privacy-focused email service) needed a better solution for DNS.
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
<summary>After years of using the Node.js internal DNS module, we ran into these recurring patterns:</summary>
|
|
92
|
+
After years of using the Node.js internal DNS module, we ran into these recurring patterns:
|
|
94
93
|
|
|
95
94
|
* [Cloudflare](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/) and [Google](https://developers.google.com/speed/public-dns/docs/doh/) now have DNS over HTTPS servers ("DoH") available – and browsers such as Mozilla Firefox now have it [enabled by default](https://support.mozilla.org/en-US/kb/firefox-dns-over-https).
|
|
96
95
|
* DNS cache consistency across multiple servers cannot be easily accomplished using packages such as `unbound`, `dnsmasq`, and `bind` – and configuring `/etc/resolv.conf` across multiple Ubuntu versions is not enjoyable (even with Ansible). Maintaining logic at the application layer is much easier from a development, deployment, and maintenance perspective.
|
|
@@ -107,8 +106,6 @@ Our team at [Forward Email](https://forwardemail.net) (100% open-source and priv
|
|
|
107
106
|
* Writing tests against DNS-related infrastructure requires either hacky DNS mocking or a DNS server (manipulating cache is much easier).
|
|
108
107
|
* <u>**The Node.js community is lacking a high-quality and dummy-proof userland DNS package with sensible defaults.**</u>
|
|
109
108
|
|
|
110
|
-
</details>
|
|
111
|
-
|
|
112
109
|
### Why integrate DNS over HTTPS
|
|
113
110
|
|
|
114
111
|
> With DNS over HTTPS (DoH), DNS queries and responses are encrypted and sent via the HTTP or HTTP/2 protocols. DoH ensures that attackers cannot forge or alter DNS traffic. DoH uses port 443, which is the standard HTTPS traffic port, to wrap the DNS query in an HTTPS request. DNS queries and responses are camouflaged within other HTTPS traffic, since it all comes and goes from the same port. – [Cloudflare](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/)
|
|
@@ -133,8 +130,7 @@ Thanks to the authors of [dohdec](https://github.com/hildjj/dohdec), [dns-packet
|
|
|
133
130
|
* HTTP error codes are mapped to DNS error codes (the error `code` and `errno` properties will appear as if they're from `dns` usage). This is a configurable option enabled by default (see `returnHTTPErrors` option).
|
|
134
131
|
* If you need callbacks, then use [util.callbackify](https://nodejs.org/api/util.html#utilcallbackifyoriginal) (e.g. `const resolveTxt = callbackify(tangerine.resolveTxt)`).
|
|
135
132
|
|
|
136
|
-
|
|
137
|
-
<summary>We have also added several improvements and new features:</summary>
|
|
133
|
+
We have also added several improvements and new features:
|
|
138
134
|
|
|
139
135
|
* Default name servers used have been set to [Cloudflare's](https://1.1.1.1/) (`['1.1.1.1', '1.0.0.1']`) (as opposed to the system default – which is often set to a default which is not privacy-focused or simply forgotten to be set by DevOps teams). You may also want to use [Cloudflare's Malware and Adult Content Blocking](https://blog.cloudflare.com/introducing-1-1-1-1-for-families/) DNS server addresses instead.
|
|
140
136
|
* You can pass a custom `servers` option (as opposed to having to invoke `dns.setServers(...)` or `resolver.setServers(...)`).
|
|
@@ -146,10 +142,7 @@ Thanks to the authors of [dohdec](https://github.com/hildjj/dohdec), [dns-packet
|
|
|
146
142
|
* Debug via `NODE_DEBUG=tangerine node app.js` flag (uses [util.debuglog](https://nodejs.org/api/util.html#utildebuglogsection-callback)).
|
|
147
143
|
* The method `setLocalAddress()` will parse the IP address and port properly to pass along for use with the agent as `localAddress` and `localPort`. If you require IPv6 addresses with ports, you must encode it as `[IPv6]:PORT` ([similar to RFC 3986](https://serverfault.com/a/205794)).
|
|
148
144
|
|
|
149
|
-
</
|
|
150
|
-
|
|
151
|
-
<details>
|
|
152
|
-
<summary>All existing <code>syscall</code> values have been preserved:</summary>
|
|
145
|
+
All existing <code>syscall</code> values have been preserved:
|
|
153
146
|
|
|
154
147
|
* `resolveAny` → `queryAny`
|
|
155
148
|
* `resolve4` → `queryA`
|
|
@@ -166,8 +159,6 @@ Thanks to the authors of [dohdec](https://github.com/hildjj/dohdec), [dns-packet
|
|
|
166
159
|
* `resolveSoa` → `querySoa`
|
|
167
160
|
* `reverse` → `getHostByAddr`
|
|
168
161
|
|
|
169
|
-
</details>
|
|
170
|
-
|
|
171
162
|
|
|
172
163
|
## Usage and Examples
|
|
173
164
|
|
package/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const autoBind = require('auto-bind');
|
|
|
11
11
|
const getStream = require('get-stream');
|
|
12
12
|
const hostile = require('hostile');
|
|
13
13
|
const ipaddr = require('ipaddr.js');
|
|
14
|
+
const isStream = require('is-stream');
|
|
14
15
|
const mergeOptions = require('merge-options');
|
|
15
16
|
const pMap = require('p-map');
|
|
16
17
|
const pTimeout = require('p-timeout');
|
|
@@ -702,8 +703,8 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
702
703
|
|
|
703
704
|
// safeguard (matches c-ares)
|
|
704
705
|
if (lower === 'localhost' || lower === 'localhost.') {
|
|
705
|
-
|
|
706
|
-
|
|
706
|
+
resolve4 ||= ['127.0.0.1'];
|
|
707
|
+
resolve6 ||= ['::1'];
|
|
707
708
|
}
|
|
708
709
|
|
|
709
710
|
if (isIPv4(name)) {
|
|
@@ -1128,22 +1129,41 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1128
1129
|
const statusCode = response.status || response.statusCode;
|
|
1129
1130
|
debug('response', { statusCode, headers });
|
|
1130
1131
|
|
|
1132
|
+
// <https://github.com/nodejs/undici/issues/3353#issuecomment-2184635954>
|
|
1133
|
+
// eslint-disable-next-line max-depth
|
|
1134
|
+
if (body && isStream(body) && typeof body.on === 'function')
|
|
1135
|
+
body.on('error', (err) => {
|
|
1136
|
+
this.options.logger.error(err, { response });
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1131
1139
|
// eslint-disable-next-line max-depth
|
|
1132
1140
|
if (body && statusCode >= 200 && statusCode < 300) {
|
|
1141
|
+
// <https://sindresorhus.com/blog/goodbye-nodejs-buffer>
|
|
1133
1142
|
buffer = Buffer.isBuffer(body)
|
|
1134
1143
|
? body
|
|
1135
1144
|
: // eslint-disable-next-line no-await-in-loop
|
|
1136
1145
|
await getStream.buffer(body);
|
|
1146
|
+
// <https://github.com/nodejs/undici/issues/3353>
|
|
1147
|
+
// eslint-disable-next-line no-await-in-loop, max-depth
|
|
1148
|
+
if (body && typeof body.dump === 'function') await body.dump();
|
|
1137
1149
|
// eslint-disable-next-line max-depth
|
|
1138
1150
|
if (!abortController.signal.aborted) abortController.abort();
|
|
1139
1151
|
break;
|
|
1140
1152
|
}
|
|
1141
1153
|
|
|
1154
|
+
// <https://github.com/nodejs/undici/issues/3353>
|
|
1155
|
+
// eslint-disable-next-line no-await-in-loop, max-depth
|
|
1156
|
+
if (body && typeof body.dump === 'function') await body.dump();
|
|
1157
|
+
|
|
1158
|
+
// <https://github.com/nodejs/undici/blob/00dfd0bd41e73782452aecb728395f354585ca94/lib/core/errors.js#L47-L58>
|
|
1142
1159
|
const message =
|
|
1143
1160
|
http.STATUS_CODES[statusCode] ||
|
|
1144
1161
|
this.options.defaultHTTPErrorMessage;
|
|
1145
1162
|
const err = new Error(message);
|
|
1163
|
+
err.body = body;
|
|
1164
|
+
err.status = statusCode;
|
|
1146
1165
|
err.statusCode = statusCode;
|
|
1166
|
+
err.headers = headers;
|
|
1147
1167
|
throw err;
|
|
1148
1168
|
}
|
|
1149
1169
|
} catch (err) {
|
|
@@ -1202,6 +1222,12 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1202
1222
|
// that one or more dns servers have persistent issues
|
|
1203
1223
|
if (errors.length > 0)
|
|
1204
1224
|
this.options.logger.error(this.constructor.combineErrors(errors));
|
|
1225
|
+
|
|
1226
|
+
//
|
|
1227
|
+
// NOTE: dns-packet does not yet support Uint8Array
|
|
1228
|
+
// (however undici does have body.arrayBuffer() method)
|
|
1229
|
+
//
|
|
1230
|
+
// https://github.com/mafintosh/dns-packet/issues/72
|
|
1205
1231
|
return packet.decode(buffer);
|
|
1206
1232
|
} catch (_err) {
|
|
1207
1233
|
if (!abortController.signal.aborted) abortController.abort();
|
|
@@ -1886,8 +1912,8 @@ class Tangerine extends dns.promises.Resolver {
|
|
|
1886
1912
|
Buffer.isBuffer(a.data)
|
|
1887
1913
|
? a.data.toString()
|
|
1888
1914
|
: Array.isArray(a.data)
|
|
1889
|
-
|
|
1890
|
-
|
|
1915
|
+
? a.data.map((d) => (Buffer.isBuffer(d) ? d.toString() : d))
|
|
1916
|
+
: a.data
|
|
1891
1917
|
];
|
|
1892
1918
|
});
|
|
1893
1919
|
}
|
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 (with TTL and purge support).",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.5",
|
|
5
5
|
"author": "Forward Email (https://forwardemail.net)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/forwardemail/nodejs-dns-over-https-tangerine/issues"
|
|
@@ -12,49 +12,50 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@ungap/structured-clone": "^1.2.0",
|
|
14
14
|
"auto-bind": "4",
|
|
15
|
-
"dns-packet": "^5.6.
|
|
15
|
+
"dns-packet": "^5.6.1",
|
|
16
16
|
"dohdec": "^5.0.3",
|
|
17
17
|
"get-stream": "6",
|
|
18
|
-
"hostile": "^1.
|
|
19
|
-
"ipaddr.js": "^2.0
|
|
18
|
+
"hostile": "^1.4.0",
|
|
19
|
+
"ipaddr.js": "^2.2.0",
|
|
20
|
+
"is-stream": "2.0.1",
|
|
20
21
|
"merge-options": "3.0.4",
|
|
21
22
|
"p-map": "4",
|
|
22
23
|
"p-timeout": "4",
|
|
23
24
|
"p-wait-for": "3",
|
|
24
25
|
"port-numbers": "6.0.1",
|
|
25
|
-
"private-ip": "^3.0.
|
|
26
|
-
"punycode": "^2.3.
|
|
27
|
-
"semver": "^7.
|
|
26
|
+
"private-ip": "^3.0.2",
|
|
27
|
+
"punycode": "^2.3.1",
|
|
28
|
+
"semver": "^7.6.3"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@commitlint/cli": "^
|
|
31
|
-
"@commitlint/config-conventional": "^
|
|
31
|
+
"@commitlint/cli": "^19.3.0",
|
|
32
|
+
"@commitlint/config-conventional": "^19.2.2",
|
|
32
33
|
"ava": "^5.2.0",
|
|
33
|
-
"axios": "^1.
|
|
34
|
+
"axios": "^1.7.3",
|
|
34
35
|
"benchmark": "^2.1.4",
|
|
35
36
|
"cross-env": "^7.0.3",
|
|
36
|
-
"eslint": "^8.
|
|
37
|
+
"eslint": "^9.8.0",
|
|
37
38
|
"eslint-config-xo-lass": "^2.0.1",
|
|
38
|
-
"fetch-mock": "^
|
|
39
|
+
"fetch-mock": "^10.1.1",
|
|
39
40
|
"fixpack": "^4.0.0",
|
|
40
41
|
"got": "11",
|
|
41
|
-
"husky": "^
|
|
42
|
-
"ioredis": "^5.
|
|
43
|
-
"ioredis-mock": "^8.
|
|
42
|
+
"husky": "^9.1.4",
|
|
43
|
+
"ioredis": "^5.4.1",
|
|
44
|
+
"ioredis-mock": "^8.9.0",
|
|
44
45
|
"is-ci": "^3.0.1",
|
|
45
|
-
"lint-staged": "^
|
|
46
|
+
"lint-staged": "^15.2.8",
|
|
46
47
|
"lodash": "^4.17.21",
|
|
47
|
-
"nock": "^13.
|
|
48
|
+
"nock": "^13.5.4",
|
|
48
49
|
"node-fetch": "2",
|
|
49
|
-
"nyc": "^
|
|
50
|
-
"phin": "^3.7.
|
|
51
|
-
"remark-cli": "
|
|
50
|
+
"nyc": "^17.0.0",
|
|
51
|
+
"phin": "^3.7.1",
|
|
52
|
+
"remark-cli": "11.0.0",
|
|
52
53
|
"remark-preset-github": "^4.0.4",
|
|
53
54
|
"request": "^2.88.2",
|
|
54
55
|
"sort-keys": "4.2.0",
|
|
55
|
-
"superagent": "^
|
|
56
|
-
"undici": "^
|
|
57
|
-
"xo": "^0.
|
|
56
|
+
"superagent": "^9.0.2",
|
|
57
|
+
"undici": "^6.19.5",
|
|
58
|
+
"xo": "^0.58.0"
|
|
58
59
|
},
|
|
59
60
|
"engines": {
|
|
60
61
|
"node": ">=16"
|