Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions deps/undici/src/docs/docs/api/Client.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,22 +111,35 @@ added: v1.0.0
`autoSelectFamily` is enabled. **Default:** `250`.
* `allowH2` {boolean} Enables HTTP/2 support when the server assigns it a
higher priority through ALPN negotiation. **Default:** `true`.
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
* `useH2c` {boolean} _Deprecated: use h2Options.useH2c instead_ Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} _Deprecated: use h2Options.useH2c instead_ The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `initialWindowSize` {number} The HTTP/2 stream-level flow-control window
size (`SETTINGS_INITIAL_WINDOW_SIZE`). Must be a positive integer.
**Default:** `262144`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
* `connectionWindowSize` {number} _Deprecated: use h2Options.connectionWindowSize instead_ The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
* `pingInterval` {number} _Deprecated: use h2Options.pingInterval instead_ The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `h2Options` {object} Set of options for HTTP/2 sessions
* `useH2c` {boolean} Enforces h2c (HTTP/2 cleartext) for non-HTTPS
connections. **Default:** `false`.
* `maxConcurrentStreams` {number} The maximum number of concurrent HTTP/2
streams for a single session. Once h2 is negotiated this — not `pipelining`,
which is HTTP/1.1 only — is the ceiling used to dispatch in-flight requests.
It may be overridden by the server's `SETTINGS_MAX_CONCURRENT_STREAMS`
frame. **Default:** `100`.
* `connectionWindowSize` {number} The HTTP/2 connection-level flow-control
window size set via `ClientHttp2Session.setLocalWindowSize()`. Must be a
positive integer. **Default:** `524288`.
* `pingInterval` {number} The time interval, in milliseconds, between HTTP/2
PING frames. Set to `0` to disable PING frames. Applies only to HTTP/2
connections and emits a `ping` event on the client. **Default:** `60e3`.
* `settings` {object} `SETTINGS` frame options. For full reference, take a
look to [HTTP/2#Settings Object](https://nodejs.org/api/http2.html#settings-object)
* `webSocket` {Object} (optional) WebSocket-specific configuration.
* `maxFragments` {number} The maximum number of fragments in a message. Set
to `0` to disable the limit. **Default:** `131072`.
Expand Down
76 changes: 33 additions & 43 deletions deps/undici/src/lib/api/readable.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,6 @@ const kContentType = Symbol('kContentType')
const kContentLength = Symbol('kContentLength')
const kUsed = Symbol('kUsed')
const kBytesRead = Symbol('kBytesRead')
const kPreservedBuffer = Symbol('kPreservedBuffer')

const noop = () => {}

Expand DownExpand Up@@ -326,36 +325,14 @@ class BodyReadable extends Readable {
*/
setEncoding (encoding) {
if (Buffer.isEncoding(encoding)) {
// Preserve raw Buffer chunks for the consume path (body.text(),
// body.json(), etc.) before super.setEncoding() replaces them
// with decoded strings. Without this, the consume path would
// lose access to the original bytes — some of which may be held
// by the decoder for incomplete multi-byte sequences, and the
// rest converted to strings that can't be safely concatenated
// byte-wise.
const state = this._readableState
const buffer = state.buffer
if (buffer && state.length > 0) {
const bufferIndex = state.bufferIndex ?? 0
const preserved = []
const source = typeof buffer.slice === 'function'
? buffer.slice(bufferIndex)
: buffer
for (const data of source) {
if (Buffer.isBuffer(data)) {
preserved.push(data)
}
}
if (preserved.length > 0) {
this[kPreservedBuffer] = (this[kPreservedBuffer] || []).concat(preserved)
}
}

// Delegate to Node.js Readable.setEncoding() which initializes a
// StringDecoder and re-encodes already-buffered chunks. This properly
// handles multi-byte sequences split at chunk boundaries for the
// for-await / on('data') paths. Without this, Node.js uses
// buf.toString(encoding) on each chunk, producing U+FFFD for split chars.
//
// The consume path (body.text(), body.json(), ...) copes with the
// decoded strings this leaves in state.buffer, see consumeStart().
super.setEncoding(encoding)
}
return this
Expand DownExpand Up@@ -464,17 +441,7 @@ function consumeStart (consume) {

const { _readableState: state } = consume.stream

// If setEncoding() was called, state.buffer may contain decoded strings
// (which would break Buffer.concat in chunksDecode). Use the preserved
// raw Buffers (saved before super.setEncoding() in setEncoding()) for
// byte-level accurate consumption. Otherwise read from state.buffer.
const preserved = consume.stream[kPreservedBuffer]
if (preserved && preserved.length > 0) {
for (const chunk of preserved) {
consumePush(consume, chunk)
}
consume.stream[kPreservedBuffer] = null
} else if (state.bufferIndex) {
if (state.bufferIndex) {
const start = state.bufferIndex
const end = state.buffer.length
for (let n = start; n < end; n++) {
Expand All@@ -486,14 +453,29 @@ function consumeStart (consume) {
}
}

// If setEncoding() was called, state.buffer holds decoded strings, which
// consumePush() turns back into bytes. The trailing bytes of a multi-byte
// sequence split across a chunk boundary are not part of any of those
// strings, they are held inside the decoder until the rest arrives, so
// take them from there.
const decoder = state.decoder
if (decoder != null && decoder.lastNeed > 0) {
consumePush(consume, Buffer.from(decoder.lastChar.subarray(0, decoder.lastTotal - decoder.lastNeed)))
}

if (state.endEmitted) {
consumeEnd(this[kConsume], this._readableState.encoding)
} else {
consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})
// No `this` to read the consume off here: consumeStart is a free function, called from
// the queueMicrotask above. The callback below does have one, because the emitter passes
// the stream as its receiver. Returning matters too - consumeEnd() clears consume.stream,
// which the resume() below would then dereference.
consumeEnd(consume, state.encoding)
return
}

consume.stream.on('end', function () {
consumeEnd(this[kConsume], this._readableState.encoding)
})

consume.stream.resume()

while (consume.stream.read() != null) {
Expand DownExpand Up@@ -583,14 +565,22 @@ function consumeEnd (consume, encoding) {

/**
* @param {Consume} consume
* @param {Buffer} chunk
* @param {Buffer|string} chunk
* @returns {void}
*/
function consumePush (consume, chunk) {
if (consume.body === null) {
return
}

if (typeof chunk === 'string') {
// Buffered before the consume started, while an encoding was set.
// consume.length has to stay a byte count and chunksDecode()/chunksConcat()
// only work on bytes, so re-encode. A string's own length is in UTF-16 code
// units and Uint8Array.prototype.set() ignores a string argument entirely.
chunk = Buffer.from(chunk, consume.stream._readableState.encoding)
}

consume.length += chunk.length
consume.body.push(chunk)
}
Expand Down
18 changes: 16 additions & 2 deletions deps/undici/src/lib/core/connect.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP

port = port || 80

socket = net.connect({
const connectOptions = {
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
...options,
localAddress,
port,
host: hostname
})
}

const family = net.isIP(hostname)
if (family !== 0 && servername && servername !== hostname) {
connectOptions.host = servername
connectOptions.lookup = (_hostname, lookupOptions, cb) => {
if (lookupOptions.all) {
cb(null, [{ address: hostname, family }])
} else {
cb(null, hostname, family)
}
}
}

socket = net.connect(connectOptions)
if (useH2c === true) {
socket.alpnProtocol = 'h2'
}
Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ module.exports = {
kCounter: Symbol('socket request counter'),
kMaxResponseSize: Symbol('max response size'),
kHTTP2Session: Symbol('http2Session'),
kHTTP2Options: Symbol('http2 options'),
kHTTP2SessionState: Symbol('http2Session state'),
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
kConstruct: Symbol('constructable'),
Expand Down
6 changes: 3 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearImmediate(socket[kIdleSocketValidationTimeout])
clearTimeout(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All@@ -1061,14 +1061,14 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
})
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
}

Expand Down
Loading
Loading