undici 5.14.0 → 5.15.1

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.
@@ -0,0 +1,101 @@
1
+ # Cookie Handling
2
+
3
+ ## `Cookie` interface
4
+
5
+ * **name** `string`
6
+ * **value** `string`
7
+ * **expires** `Date|number` (optional)
8
+ * **maxAge** `number` (optional)
9
+ * **domain** `string` (optional)
10
+ * **path** `string` (optional)
11
+ * **secure** `boolean` (optional)
12
+ * **httpOnly** `boolean` (optional)
13
+ * **sameSite** `'String'|'Lax'|'None'` (optional)
14
+ * **unparsed** `string[]` (optional) Left over attributes that weren't parsed.
15
+
16
+ ## `deleteCookie(headers, name[, attributes])`
17
+
18
+ Sets the expiry time of the cookie to the unix epoch, causing browsers to delete it when received.
19
+
20
+ ```js
21
+ import { deleteCookie, Headers } from 'undici'
22
+
23
+ const headers = new Headers()
24
+ deleteCookie(headers, 'name')
25
+
26
+ console.log(headers.get('set-cookie')) // name=; Expires=Thu, 01 Jan 1970 00:00:00 GMT
27
+ ```
28
+
29
+ Arguments:
30
+
31
+ * **headers** `Headers`
32
+ * **name** `string`
33
+ * **attributes** `{ path?: string, domain?: string }` (optional)
34
+
35
+ Returns: `void`
36
+
37
+ ## `getCookies(headers)`
38
+
39
+ Parses the `Cookie` header and returns a list of attributes and values.
40
+
41
+ ```js
42
+ import { getCookies, Headers } from 'undici'
43
+
44
+ const headers = new Headers({
45
+ cookie: 'get=cookies; and=attributes'
46
+ })
47
+
48
+ console.log(getCookies(headers)) // { get: 'cookies', and: 'attributes' }
49
+ ```
50
+
51
+ Arguments:
52
+
53
+ * **headers** `Headers`
54
+
55
+ Returns: `Record<string, string>`
56
+
57
+ ## `getSetCookies(headers)`
58
+
59
+ Parses all `Set-Cookie` headers.
60
+
61
+ ```js
62
+ import { getSetCookies, Headers } from 'undici'
63
+
64
+ const headers = new Headers({ 'set-cookie': 'undici=getSetCookies; Secure' })
65
+
66
+ console.log(getSetCookies(headers))
67
+ // [
68
+ // {
69
+ // name: 'undici',
70
+ // value: 'getSetCookies',
71
+ // secure: true
72
+ // }
73
+ // ]
74
+
75
+ ```
76
+
77
+ Arguments:
78
+
79
+ * **headers** `Headers`
80
+
81
+ Returns: `Cookie[]`
82
+
83
+ ## `setCookie(headers, cookie)`
84
+
85
+ Appends a cookie to the `Set-Cookie` header.
86
+
87
+ ```js
88
+ import { setCookie, Headers } from 'undici'
89
+
90
+ const headers = new Headers()
91
+ setCookie(headers, { name: 'undici', value: 'setCookie' })
92
+
93
+ console.log(headers.get('Set-Cookie')) // undici=setCookie
94
+ ```
95
+
96
+ Arguments:
97
+
98
+ * **headers** `Headers`
99
+ * **cookie** `Cookie`
100
+
101
+ Returns: `void`
@@ -135,3 +135,70 @@ diagnosticsChannel.channel('undici:client:connectError').subscribe(({ error, soc
135
135
  // connector is a function that creates the socket
136
136
  console.log(`Connect failed with ${error.message}`)
137
137
  })
138
+ ```
139
+
140
+ ## `undici:websocket:open`
141
+
142
+ This message is published after the client has successfully connected to a server.
143
+
144
+ ```js
145
+ import diagnosticsChannel from 'diagnostics_channel'
146
+
147
+ diagnosticsChannel.channel('undici:websocket:open').subscribe(({ address, protocol, extensions }) => {
148
+ console.log(address) // address, family, and port
149
+ console.log(protocol) // negotiated subprotocols
150
+ console.log(extensions) // negotiated extensions
151
+ })
152
+ ```
153
+
154
+ ## `undici:websocket:close`
155
+
156
+ This message is published after the connection has closed.
157
+
158
+ ```js
159
+ import diagnosticsChannel from 'diagnostics_channel'
160
+
161
+ diagnosticsChannel.channel('undici:websocket:close').subscribe(({ websocket, code, reason }) => {
162
+ console.log(websocket) // the WebSocket object
163
+ console.log(code) // the closing status code
164
+ console.log(reason) // the closing reason
165
+ })
166
+ ```
167
+
168
+ ## `undici:websocket:socket_error`
169
+
170
+ This message is published if the socket experiences an error.
171
+
172
+ ```js
173
+ import diagnosticsChannel from 'diagnostics_channel'
174
+
175
+ diagnosticsChannel.channel('undici:websocket:socket_error').subscribe((error) => {
176
+ console.log(error)
177
+ })
178
+ ```
179
+
180
+ ## `undici:websocket:ping`
181
+
182
+ This message is published after the client receives a ping frame, if the connection is not closing.
183
+
184
+ ```js
185
+ import diagnosticsChannel from 'diagnostics_channel'
186
+
187
+ diagnosticsChannel.channel('undici:websocket:ping').subscribe(({ payload }) => {
188
+ // a Buffer or undefined, containing the optional application data of the frame
189
+ console.log(payload)
190
+ })
191
+ ```
192
+
193
+ ## `undici:websocket:pong`
194
+
195
+ This message is published after the client receives a pong frame.
196
+
197
+ ```js
198
+ import diagnosticsChannel from 'diagnostics_channel'
199
+
200
+ diagnosticsChannel.channel('undici:websocket:pong').subscribe(({ payload }) => {
201
+ // a Buffer or undefined, containing the optional application data of the frame
202
+ console.log(payload)
203
+ })
204
+ ```
@@ -192,6 +192,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
192
192
  * **origin** `string | URL`
193
193
  * **path** `string`
194
194
  * **method** `string`
195
+ * **reset** `boolean` (optional) - Default: `false` - If `false`, the request will attempt to create a long-living connection by sending the `connection: keep-alive` header,otherwise will attempt to close it immediately after response by sending `connection: close` within the request and closing the socket afterwards.
195
196
  * **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null`
196
197
  * **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
197
198
  * **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
@@ -0,0 +1,25 @@
1
+ # Fetch
2
+
3
+ Undici exposes a fetch() method starts the process of fetching a resource from the network.
4
+
5
+ Documentation and examples can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/fetch).
6
+
7
+ ## File
8
+
9
+ This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)
10
+
11
+ ## FormData
12
+
13
+ This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
14
+
15
+ ## Response
16
+
17
+ This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response)
18
+
19
+ ## Request
20
+
21
+ This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Request)
22
+
23
+ ## Header
24
+
25
+ This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
@@ -0,0 +1,20 @@
1
+ # Class: WebSocket
2
+
3
+ > ⚠️ Warning: the WebSocket API is experimental and has known bugs.
4
+
5
+ Extends: [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
6
+
7
+ The WebSocket object provides a way to manage a WebSocket connection to a server, allowing bidirectional communication. The API follows the [WebSocket spec](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket).
8
+
9
+ ## `new WebSocket(url[, protocol])`
10
+
11
+ Arguments:
12
+
13
+ * **url** `URL | string` - The url's protocol *must* be `ws` or `wss`.
14
+ * **protocol** `string | string[]` (optional) - Subprotocol(s) to request the server use.
15
+
16
+ ## Read More
17
+
18
+ - [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
19
+ - [The WebSocket Specification](https://www.rfc-editor.org/rfc/rfc6455)
20
+ - [The WHATWG WebSocket Specification](https://websockets.spec.whatwg.org/)
package/index.d.ts CHANGED
@@ -16,11 +16,13 @@ import mockErrors from'./types/mock-errors'
16
16
  import ProxyAgent from'./types/proxy-agent'
17
17
  import { request, pipeline, stream, connect, upgrade } from './types/api'
18
18
 
19
+ export * from './types/cookies'
19
20
  export * from './types/fetch'
20
21
  export * from './types/file'
21
22
  export * from './types/filereader'
22
23
  export * from './types/formdata'
23
24
  export * from './types/diagnostics-channel'
25
+ export * from './types/websocket'
24
26
  export { Interceptable } from './types/mock-interceptor'
25
27
 
26
28
  export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler }
package/index.js CHANGED
@@ -119,6 +119,21 @@ if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) {
119
119
  module.exports.getGlobalOrigin = getGlobalOrigin
120
120
  }
121
121
 
122
+ if (nodeMajor >= 16) {
123
+ const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies')
124
+
125
+ module.exports.deleteCookie = deleteCookie
126
+ module.exports.getCookies = getCookies
127
+ module.exports.getSetCookies = getSetCookies
128
+ module.exports.setCookie = setCookie
129
+ }
130
+
131
+ if (nodeMajor >= 18) {
132
+ const { WebSocket } = require('./lib/websocket/websocket')
133
+
134
+ module.exports.WebSocket = WebSocket
135
+ }
136
+
122
137
  module.exports.request = makeDispatcher(api.request)
123
138
  module.exports.stream = makeDispatcher(api.stream)
124
139
  module.exports.pipeline = makeDispatcher(api.pipeline)
package/lib/client.js CHANGED
@@ -441,6 +441,7 @@ class Parser {
441
441
 
442
442
  this.keepAlive = ''
443
443
  this.contentLength = ''
444
+ this.connection = ''
444
445
  this.maxResponseSize = client[kMaxResponseSize]
445
446
  }
446
447
 
@@ -616,6 +617,8 @@ class Parser {
616
617
  const key = this.headers[len - 2]
617
618
  if (key.length === 10 && key.toString().toLowerCase() === 'keep-alive') {
618
619
  this.keepAlive += buf.toString()
620
+ } else if (key.length === 10 && key.toString().toLowerCase() === 'connection') {
621
+ this.connection += buf.toString()
619
622
  } else if (key.length === 14 && key.toString().toLowerCase() === 'content-length') {
620
623
  this.contentLength += buf.toString()
621
624
  }
@@ -709,7 +712,11 @@ class Parser {
709
712
  assert.strictEqual(this.timeoutType, TIMEOUT_HEADERS)
710
713
 
711
714
  this.statusCode = statusCode
712
- this.shouldKeepAlive = shouldKeepAlive
715
+ this.shouldKeepAlive = (
716
+ shouldKeepAlive ||
717
+ // Override llhttp value which does not allow keepAlive for HEAD.
718
+ (request.method === 'HEAD' && !socket[kReset] && this.connection.toLowerCase() === 'keep-alive')
719
+ )
713
720
 
714
721
  if (this.statusCode >= 200) {
715
722
  const bodyTimeout = request.bodyTimeout != null
@@ -739,7 +746,7 @@ class Parser {
739
746
  this.headers = []
740
747
  this.headersSize = 0
741
748
 
742
- if (shouldKeepAlive && client[kPipelining]) {
749
+ if (this.shouldKeepAlive && client[kPipelining]) {
743
750
  const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null
744
751
 
745
752
  if (keepAliveTimeout != null) {
@@ -769,7 +776,6 @@ class Parser {
769
776
  }
770
777
 
771
778
  if (request.method === 'HEAD') {
772
- assert(socket[kReset])
773
779
  return 1
774
780
  }
775
781
 
@@ -843,6 +849,7 @@ class Parser {
843
849
  this.bytesRead = 0
844
850
  this.contentLength = ''
845
851
  this.keepAlive = ''
852
+ this.connection = ''
846
853
 
847
854
  assert(this.headers.length % 2 === 0)
848
855
  this.headers = []
@@ -1295,7 +1302,7 @@ function _resume (client, sync) {
1295
1302
  }
1296
1303
 
1297
1304
  function write (client, request) {
1298
- const { body, method, path, host, upgrade, headers, blocking } = request
1305
+ const { body, method, path, host, upgrade, headers, blocking, reset } = request
1299
1306
 
1300
1307
  // https://tools.ietf.org/html/rfc7231#section-4.3.1
1301
1308
  // https://tools.ietf.org/html/rfc7231#section-4.3.2
@@ -1363,7 +1370,6 @@ function write (client, request) {
1363
1370
 
1364
1371
  if (method === 'HEAD') {
1365
1372
  // https://github.com/mcollina/undici/issues/258
1366
-
1367
1373
  // Close after a HEAD request to interop with misbehaving servers
1368
1374
  // that may send a body in the response.
1369
1375
 
@@ -1377,6 +1383,10 @@ function write (client, request) {
1377
1383
  socket[kReset] = true
1378
1384
  }
1379
1385
 
1386
+ if (reset != null) {
1387
+ socket[kReset] = reset
1388
+ }
1389
+
1380
1390
  if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) {
1381
1391
  socket[kReset] = true
1382
1392
  }
@@ -1395,7 +1405,7 @@ function write (client, request) {
1395
1405
 
1396
1406
  if (upgrade) {
1397
1407
  header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n`
1398
- } else if (client[kPipelining]) {
1408
+ } else if (client[kPipelining] && !socket[kReset]) {
1399
1409
  header += 'connection: keep-alive\r\n'
1400
1410
  } else {
1401
1411
  header += 'connection: close\r\n'
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ // https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size
4
+ const maxAttributeValueSize = 1024
5
+
6
+ // https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size
7
+ const maxNameValuePairSize = 4096
8
+
9
+ module.exports = {
10
+ maxAttributeValueSize,
11
+ maxNameValuePairSize
12
+ }
@@ -0,0 +1,183 @@
1
+ 'use strict'
2
+
3
+ const { parseSetCookie } = require('./parse')
4
+ const { stringify, getHeadersList } = require('./util')
5
+ const { webidl } = require('../fetch/webidl')
6
+ const { Headers } = require('../fetch/headers')
7
+
8
+ /**
9
+ * @typedef {Object} Cookie
10
+ * @property {string} name
11
+ * @property {string} value
12
+ * @property {Date|number|undefined} expires
13
+ * @property {number|undefined} maxAge
14
+ * @property {string|undefined} domain
15
+ * @property {string|undefined} path
16
+ * @property {boolean|undefined} secure
17
+ * @property {boolean|undefined} httpOnly
18
+ * @property {'Strict'|'Lax'|'None'} sameSite
19
+ * @property {string[]} unparsed
20
+ */
21
+
22
+ /**
23
+ * @param {Headers} headers
24
+ * @returns {Record<string, string>}
25
+ */
26
+ function getCookies (headers) {
27
+ webidl.argumentLengthCheck(arguments, 1, { header: 'getCookies' })
28
+
29
+ webidl.brandCheck(headers, Headers, { strict: false })
30
+
31
+ const cookie = headers.get('cookie')
32
+ const out = {}
33
+
34
+ if (!cookie) {
35
+ return out
36
+ }
37
+
38
+ for (const piece of cookie.split(';')) {
39
+ const [name, ...value] = piece.split('=')
40
+
41
+ out[name.trim()] = value.join('=')
42
+ }
43
+
44
+ return out
45
+ }
46
+
47
+ /**
48
+ * @param {Headers} headers
49
+ * @param {string} name
50
+ * @param {{ path?: string, domain?: string }|undefined} attributes
51
+ * @returns {void}
52
+ */
53
+ function deleteCookie (headers, name, attributes) {
54
+ webidl.argumentLengthCheck(arguments, 2, { header: 'deleteCookie' })
55
+
56
+ webidl.brandCheck(headers, Headers, { strict: false })
57
+
58
+ name = webidl.converters.DOMString(name)
59
+ attributes = webidl.converters.DeleteCookieAttributes(attributes)
60
+
61
+ // Matches behavior of
62
+ // https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278
63
+ setCookie(headers, {
64
+ name,
65
+ value: '',
66
+ expires: new Date(0),
67
+ ...attributes
68
+ })
69
+ }
70
+
71
+ /**
72
+ * @param {Headers} headers
73
+ * @returns {Cookie[]}
74
+ */
75
+ function getSetCookies (headers) {
76
+ webidl.argumentLengthCheck(arguments, 1, { header: 'getSetCookies' })
77
+
78
+ webidl.brandCheck(headers, Headers, { strict: false })
79
+
80
+ const cookies = getHeadersList(headers).cookies
81
+
82
+ if (!cookies) {
83
+ return []
84
+ }
85
+
86
+ return cookies.map((pair) => parseSetCookie(pair[1]))
87
+ }
88
+
89
+ /**
90
+ * @param {Headers} headers
91
+ * @param {Cookie} cookie
92
+ * @returns {void}
93
+ */
94
+ function setCookie (headers, cookie) {
95
+ webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' })
96
+
97
+ webidl.brandCheck(headers, Headers, { strict: false })
98
+
99
+ cookie = webidl.converters.Cookie(cookie)
100
+
101
+ const str = stringify(cookie)
102
+
103
+ if (str) {
104
+ headers.append('Set-Cookie', stringify(cookie))
105
+ }
106
+ }
107
+
108
+ webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([
109
+ {
110
+ converter: webidl.nullableConverter(webidl.converters.DOMString),
111
+ key: 'path',
112
+ defaultValue: null
113
+ },
114
+ {
115
+ converter: webidl.nullableConverter(webidl.converters.DOMString),
116
+ key: 'domain',
117
+ defaultValue: null
118
+ }
119
+ ])
120
+
121
+ webidl.converters.Cookie = webidl.dictionaryConverter([
122
+ {
123
+ converter: webidl.converters.DOMString,
124
+ key: 'name'
125
+ },
126
+ {
127
+ converter: webidl.converters.DOMString,
128
+ key: 'value'
129
+ },
130
+ {
131
+ converter: webidl.nullableConverter((value) => {
132
+ if (typeof value === 'number') {
133
+ return webidl.converters['unsigned long long'](value)
134
+ }
135
+
136
+ return new Date(value)
137
+ }),
138
+ key: 'expires',
139
+ defaultValue: null
140
+ },
141
+ {
142
+ converter: webidl.nullableConverter(webidl.converters['long long']),
143
+ key: 'maxAge',
144
+ defaultValue: null
145
+ },
146
+ {
147
+ converter: webidl.nullableConverter(webidl.converters.DOMString),
148
+ key: 'domain',
149
+ defaultValue: null
150
+ },
151
+ {
152
+ converter: webidl.nullableConverter(webidl.converters.DOMString),
153
+ key: 'path',
154
+ defaultValue: null
155
+ },
156
+ {
157
+ converter: webidl.nullableConverter(webidl.converters.boolean),
158
+ key: 'secure',
159
+ defaultValue: null
160
+ },
161
+ {
162
+ converter: webidl.nullableConverter(webidl.converters.boolean),
163
+ key: 'httpOnly',
164
+ defaultValue: null
165
+ },
166
+ {
167
+ converter: webidl.converters.USVString,
168
+ key: 'sameSite',
169
+ allowedValues: ['Strict', 'Lax', 'None']
170
+ },
171
+ {
172
+ converter: webidl.sequenceConverter(webidl.converters.DOMString),
173
+ key: 'unparsed',
174
+ defaultValue: []
175
+ }
176
+ ])
177
+
178
+ module.exports = {
179
+ getCookies,
180
+ deleteCookie,
181
+ getSetCookies,
182
+ setCookie
183
+ }