@nxtedition/nxt-undici 6.1.6 → 6.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.
@@ -95,7 +95,6 @@ class CacheHandler extends DecoratorHandler {
95
95
  }
96
96
 
97
97
  const cachedAt = Date.now()
98
-
99
98
  this.#value = {
100
99
  body: [],
101
100
  size: 0,
@@ -104,7 +103,7 @@ class CacheHandler extends DecoratorHandler {
104
103
  statusMessage: '',
105
104
  headers,
106
105
  cacheControlDirectives,
107
- etag: headers.etag,
106
+ etag: typeof headers.etag === 'string' && isEtagUsable(headers.etag) ? headers.etag : '',
108
107
  vary,
109
108
  cachedAt,
110
109
  staleAt: 0,
@@ -213,3 +212,37 @@ export default () => (dispatch) => (opts, handler) => {
213
212
  abort(err)
214
213
  }
215
214
  }
215
+
216
+ /**
217
+ * Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
218
+ * however, including them in cached resposnes serves little to no purpose.
219
+ *
220
+ * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-etag
221
+ *
222
+ * @param {string} etag
223
+ * @returns {boolean}
224
+ */
225
+ function isEtagUsable(etag) {
226
+ if (etag.length <= 2) {
227
+ // Shortest an etag can be is two chars (just ""). This is where we deviate
228
+ // from the spec requiring a min of 3 chars however
229
+ return false
230
+ }
231
+
232
+ if (etag[0] === '"' && etag[etag.length - 1] === '"') {
233
+ // ETag: ""asd123"" or ETag: "W/"asd123"", kinda undefined behavior in the
234
+ // spec. Some servers will accept these while others don't.
235
+ // ETag: "asd123"
236
+ return !(etag[1] === '"' || etag.startsWith('"W/'))
237
+ }
238
+
239
+ if (etag.startsWith('W/"') && etag[etag.length - 1] === '"') {
240
+ // ETag: W/"", also where we deviate from the spec & require a min of 3
241
+ // chars
242
+ // ETag: for W/"", W/"asd123"
243
+ return etag.length !== 4
244
+ }
245
+
246
+ // Anything else
247
+ return false
248
+ }
@@ -28,11 +28,28 @@ class Handler extends DecoratorHandler {
28
28
  }
29
29
  }
30
30
 
31
+ class DefaultCache {
32
+ #map = new Map()
33
+
34
+ set(hostname, records) {
35
+ this.#map.set(hostname, records)
36
+ }
37
+
38
+ get(hostname) {
39
+ return this.#map.get(hostname)
40
+ }
41
+
42
+ delete(hostname) {
43
+ this.#map.delete(hostname)
44
+ }
45
+ }
46
+
47
+ const DEFAULT_CACHE = new DefaultCache()
48
+
31
49
  export default () => (dispatch) => {
32
- const cache = new Map()
33
50
  const promises = new Map()
34
51
 
35
- function resolve(hostname, { resolve4, logger }) {
52
+ function resolve(hostname, { cache, resolve4, logger }) {
36
53
  let promise = promises.get(hostname)
37
54
  if (!promise) {
38
55
  logger?.debug({ dns: { hostname } }, 'lookup started')
@@ -51,7 +68,7 @@ export default () => (dispatch) => {
51
68
  counter: 0,
52
69
  }))
53
70
 
54
- logger?.debug({ err, dns: { records } }, 'lookup completed')
71
+ logger?.debug({ err, dns: { hostname, records } }, 'lookup completed')
55
72
 
56
73
  cache.set(hostname, val)
57
74
 
@@ -79,6 +96,7 @@ export default () => (dispatch) => {
79
96
 
80
97
  try {
81
98
  const { host, hostname } = origin
99
+ const cache = opts.dns.cache ?? DEFAULT_CACHE
82
100
 
83
101
  const now = getFastNow()
84
102
 
@@ -88,7 +106,7 @@ export default () => (dispatch) => {
88
106
  const logger = opts.dns.logger ?? opts.logger
89
107
 
90
108
  if (records == null || records.every((x) => x.expires < now)) {
91
- const [err, val] = await resolve(hostname, { resolve4, logger })
109
+ const [err, val] = await resolve(hostname, { cache, resolve4, logger })
92
110
 
93
111
  if (err) {
94
112
  throw err
@@ -98,7 +116,7 @@ export default () => (dispatch) => {
98
116
 
99
117
  records = val
100
118
  } else if (records.some((x) => x.expires < now + 1e3)) {
101
- resolve(hostname, { resolve4, logger })
119
+ resolve(hostname, { cache, resolve4, logger })
102
120
  }
103
121
 
104
122
  records.sort(
package/lib/utils.js CHANGED
@@ -202,6 +202,7 @@ export class DecoratorHandler {
202
202
  #aborted = false
203
203
  #errored = false
204
204
  #completed = false
205
+ #abort
205
206
 
206
207
  constructor(handler) {
207
208
  if (typeof handler !== 'object' || handler === null) {
@@ -214,23 +215,26 @@ export class DecoratorHandler {
214
215
  this.#aborted = false
215
216
  this.#errored = false
216
217
  this.#completed = false
218
+ this.#abort = abort
217
219
 
218
220
  return this.#handler.onConnect?.((reason) => {
219
221
  if (!this.#aborted && !this.#completed && !this.#errored) {
220
222
  this.#aborted = true
221
- abort(reason)
223
+ this.#abort(reason)
222
224
  }
223
225
  })
224
226
  }
225
227
 
226
228
  onUpgrade(statusCode, headers, socket) {
227
229
  if (!this.#aborted && !this.#errored) {
230
+ assert(!this.#completed)
228
231
  return this.#handler.onUpgrade?.(statusCode, headers, socket)
229
232
  }
230
233
  }
231
234
 
232
235
  onHeaders(statusCode, headers, resume) {
233
236
  if (!this.#aborted && !this.#errored) {
237
+ assert(!this.#completed)
234
238
  return this.#handler.onHeaders?.(statusCode, headers, resume)
235
239
  }
236
240
  }
@@ -243,9 +247,12 @@ export class DecoratorHandler {
243
247
  }
244
248
 
245
249
  onComplete(trailers) {
246
- if (!this.#aborted && !this.#completed) {
250
+ if (!this.#aborted && !this.#completed && !this.#errored) {
251
+ <<<<<<< HEAD
252
+ this.#abort = null
253
+ =======
254
+ >>>>>>> 23823e3 (fix: decoerator)
247
255
  this.#completed = true
248
- assert(!this.#errored)
249
256
  return this.#handler.onComplete?.(trailers)
250
257
  }
251
258
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "6.1.6",
3
+ "version": "6.2.0",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",