undici 5.13.0 → 5.15.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 (44) hide show
  1. package/docs/api/Connector.md +3 -1
  2. package/docs/api/Cookies.md +101 -0
  3. package/docs/api/DiagnosticsChannel.md +67 -0
  4. package/docs/api/Dispatcher.md +1 -0
  5. package/docs/api/Fetch.md +25 -0
  6. package/docs/api/WebSocket.md +20 -0
  7. package/index.d.ts +2 -0
  8. package/index.js +15 -0
  9. package/lib/client.js +6 -3
  10. package/lib/cookies/constants.js +12 -0
  11. package/lib/cookies/index.js +183 -0
  12. package/lib/cookies/parse.js +317 -0
  13. package/lib/cookies/util.js +291 -0
  14. package/lib/core/connect.js +62 -19
  15. package/lib/core/request.js +30 -5
  16. package/lib/core/symbols.js +1 -1
  17. package/lib/fetch/body.js +137 -168
  18. package/lib/fetch/file.js +9 -6
  19. package/lib/fetch/formdata.js +5 -2
  20. package/lib/fetch/headers.js +39 -10
  21. package/lib/fetch/index.js +67 -23
  22. package/lib/fetch/request.js +5 -2
  23. package/lib/fetch/response.js +1 -1
  24. package/lib/fetch/symbols.js +2 -1
  25. package/lib/fetch/util.js +67 -38
  26. package/lib/fetch/webidl.js +16 -4
  27. package/lib/fileapi/filereader.js +30 -0
  28. package/lib/fileapi/util.js +3 -9
  29. package/lib/websocket/connection.js +324 -0
  30. package/lib/websocket/constants.js +51 -0
  31. package/lib/websocket/events.js +303 -0
  32. package/lib/websocket/frame.js +66 -0
  33. package/lib/websocket/receiver.js +344 -0
  34. package/lib/websocket/symbols.js +15 -0
  35. package/lib/websocket/util.js +200 -0
  36. package/lib/websocket/websocket.js +559 -0
  37. package/package.json +10 -7
  38. package/types/connector.d.ts +3 -1
  39. package/types/cookies.d.ts +28 -0
  40. package/types/dispatcher.d.ts +2 -0
  41. package/types/patch.d.ts +20 -0
  42. package/types/proxy-agent.d.ts +3 -3
  43. package/types/webidl.d.ts +7 -2
  44. package/types/websocket.d.ts +121 -0
@@ -24,8 +24,10 @@ Once you call `buildConnector`, it will return a connector function, which takes
24
24
  * **hostname** `string` (required)
25
25
  * **host** `string` (optional)
26
26
  * **protocol** `string` (required)
27
- * **port** `number` (required)
27
+ * **port** `string` (required)
28
28
  * **servername** `string` (optional)
29
+ * **localAddress** `string | null` (optional) Local address the socket should connect from.
30
+ * **httpSocket** `Socket` (optional) Establish secure connection on a given socket rather than creating a new socket. It can only be sent on TLS update.
29
31
 
30
32
  ### Basic example
31
33
 
@@ -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
@@ -1295,7 +1295,7 @@ function _resume (client, sync) {
1295
1295
  }
1296
1296
 
1297
1297
  function write (client, request) {
1298
- const { body, method, path, host, upgrade, headers, blocking } = request
1298
+ const { body, method, path, host, upgrade, headers, blocking, reset } = request
1299
1299
 
1300
1300
  // https://tools.ietf.org/html/rfc7231#section-4.3.1
1301
1301
  // https://tools.ietf.org/html/rfc7231#section-4.3.2
@@ -1363,7 +1363,6 @@ function write (client, request) {
1363
1363
 
1364
1364
  if (method === 'HEAD') {
1365
1365
  // https://github.com/mcollina/undici/issues/258
1366
-
1367
1366
  // Close after a HEAD request to interop with misbehaving servers
1368
1367
  // that may send a body in the response.
1369
1368
 
@@ -1377,6 +1376,10 @@ function write (client, request) {
1377
1376
  socket[kReset] = true
1378
1377
  }
1379
1378
 
1379
+ if (reset) {
1380
+ socket[kReset] = true
1381
+ }
1382
+
1380
1383
  if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) {
1381
1384
  socket[kReset] = true
1382
1385
  }
@@ -1395,7 +1398,7 @@ function write (client, request) {
1395
1398
 
1396
1399
  if (upgrade) {
1397
1400
  header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n`
1398
- } else if (client[kPipelining]) {
1401
+ } else if (client[kPipelining] && !socket[kReset]) {
1399
1402
  header += 'connection: keep-alive\r\n'
1400
1403
  } else {
1401
1404
  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
+ }