diff --git a/.githooks/pre-commit b/.githooks/pre-commit
index 80ea62c..df8a371 100755
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -4,10 +4,5 @@ set -eu
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
-echo "pre-commit: running lint autofix"
-npm run lint:fix
-
-echo "pre-commit: running formatter"
-npm run format
-
-git add -A -- src
+echo "pre-commit: formatting and linting staged files"
+npm run lint:staged
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3f91c76..f9d9c59 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -53,10 +53,10 @@ jobs:
runs-on: ${{ matrix.settings.host }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version: 20
@@ -76,7 +76,7 @@ jobs:
run: ${{ matrix.settings.build }}
- name: Upload artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: bindings-${{ matrix.settings.target }}
path: rust/*.node
@@ -102,10 +102,10 @@ jobs:
- target: x86_64-pc-windows-msvc
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
@@ -138,7 +138,7 @@ jobs:
run: npm ci
- name: Download artifact
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v8
with:
name: bindings-${{ matrix.settings.target }}
path: artifacts
@@ -167,10 +167,10 @@ jobs:
id-token: write
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
diff --git a/.github/workflows/dependency-audit.yml b/.github/workflows/dependency-audit.yml
index bac41b6..60db98e 100644
--- a/.github/workflows/dependency-audit.yml
+++ b/.github/workflows/dependency-audit.yml
@@ -14,10 +14,10 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version-file: package.json
cache: npm
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7bcc36f..293f399 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -33,10 +33,10 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
@@ -45,7 +45,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v6
with:
path: |
~/.cargo/bin/
@@ -84,10 +84,10 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version: 20
cache: 'npm'
diff --git a/.github/workflows/wreq-upstream.yml b/.github/workflows/wreq-upstream.yml
index 6da3591..836b97b 100644
--- a/.github/workflows/wreq-upstream.yml
+++ b/.github/workflows/wreq-upstream.yml
@@ -19,12 +19,12 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v7
with:
node-version-file: package.json
cache: npm
@@ -54,7 +54,7 @@ jobs:
- name: Create pull request
if: steps.changes.outputs.changed == 'true'
- uses: peter-evans/create-pull-request@v7
+ uses: peter-evans/create-pull-request@v8
with:
branch: chore/update-wreq-upstream
delete-branch: true
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
index 26d3627..6da4951 100644
--- a/.oxfmtrc.json
+++ b/.oxfmtrc.json
@@ -2,12 +2,17 @@
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"sortPackageJson": false,
"sortImports": {
+ "groups": [["type"], ["external"], ["builtin"], ["parent", "sibling"], "index"],
"sortSideEffects": true,
"newlinesBetween": false
},
"ignorePatterns": [
"**/dist/**",
"**/node_modules/**",
+ "**/.release/**",
+ "**/.release-stubs/**",
+ "**/.tmp-release/**",
+ "**/artifacts/**"
],
"semi": true,
"trailingComma": "es5",
@@ -16,4 +21,4 @@
"tabWidth": 2,
"printWidth": 100,
"endOfLine": "lf"
-}
\ No newline at end of file
+}
diff --git a/.oxlintrc.json b/.oxlintrc.json
index 68d641b..58f103b 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -1,41 +1,164 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
+ "plugins": ["typescript", "import", "unicorn"],
"jsPlugins": [
{
"name": "stylistic",
"specifier": "@stylistic/eslint-plugin"
}
],
+ "categories": {
+ "correctness": "error",
+ "suspicious": "warn"
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es2022": true
+ },
+ "ignorePatterns": [
+ "**/dist/**",
+ "**/node_modules/**",
+ "**/.release/**",
+ "**/.release-stubs/**",
+ "**/.tmp-release/**",
+ "**/artifacts/**",
+ "**/src/generated/**",
+ "**/src/config/generated/**"
+ ],
"rules": {
+ "curly": ["error", "all"],
+ "eqeqeq": ["error", "always", { "null": "ignore" }],
+ "import-x/no-duplicates": "warn",
+ "import/no-unassigned-import": "off",
+ "no-debugger": "error",
+ "no-eval": "error",
+ "no-implied-eval": "error",
+ "no-new-func": "error",
+ "no-underscore-dangle": "off",
+ "no-var": "error",
+ "prefer-const": "error",
+ "radix": "error",
"stylistic/padding-line-between-statements": [
"error",
{
"blankLine": "always",
- "prev": [
- "const",
- "let",
- "var"
- ],
+ "prev": ["const", "let", "var"],
"next": "*"
},
{
"blankLine": "always",
"prev": "*",
- "next": "return"
+ "next": ["const", "let", "var"]
},
{
"blankLine": "any",
- "prev": [
- "const",
- "let",
- "var"
- ],
- "next": [
- "const",
- "let",
- "var"
- ]
+ "prev": ["const", "let", "var"],
+ "next": ["const", "let", "var"]
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": "block-like"
+ },
+ {
+ "blankLine": "always",
+ "prev": "block-like",
+ "next": "*"
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']"
+ }
+ },
+ {
+ "blankLine": "never",
+ "prev": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']"
+ },
+ "next": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']"
+ }
+ },
+ {
+ "blankLine": "always",
+ "prev": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']"
+ },
+ "next": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']",
+ "lineMode": "multiline"
+ }
+ },
+ {
+ "blankLine": "always",
+ "prev": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']",
+ "lineMode": "multiline"
+ },
+ "next": {
+ "selector": "ExpressionStatement[expression.type='AwaitExpression']"
+ }
+ },
+ {
+ "blankLine": "always",
+ "prev": {
+ "selector": ":not(ImportDeclaration)",
+ "lineMode": "multiline"
+ },
+ "next": "*"
+ },
+ {
+ "blankLine": "always",
+ "prev": {
+ "selector": "ImportDeclaration"
+ },
+ "next": {
+ "selector": ":not(ImportDeclaration)"
+ }
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": "return"
}
- ]
- }
+ ],
+ "@typescript-eslint/no-explicit-any": "warn",
+ "@typescript-eslint/no-require-imports": "error",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ "argsIgnorePattern": "^_",
+ "varsIgnorePattern": "^_",
+ "caughtErrorsIgnorePattern": "^_"
+ }
+ ],
+ "unicorn/filename-case": [
+ "error",
+ {
+ "cases": {
+ "camelCase": true,
+ "kebabCase": true,
+ "pascalCase": true
+ }
+ }
+ ],
+ "unicorn/prefer-node-protocol": "error"
+ },
+ "overrides": [
+ {
+ "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
+ "rules": {
+ "no-undef": "off"
+ }
+ },
+ {
+ "files": ["src/native/binding.ts", "scripts/generate-browser-profiles.mjs"],
+ "rules": {
+ "@typescript-eslint/no-require-imports": "off"
+ }
+ }
+ ]
}
diff --git a/README.md b/README.md
index fbc17df..e347535 100644
--- a/README.md
+++ b/README.md
@@ -105,7 +105,12 @@ console.log(await response.json());
import { fetch } from 'node-wreq';
const response = await fetch('https://httpbin.org/get', {
- browser: 'firefox_139',
+ browser: {
+ profile: 'firefox_151',
+ platform: 'linux',
+ http2: true,
+ headers: true,
+ },
query: {
source: 'node-wreq',
debug: true,
@@ -142,7 +147,10 @@ console.log(await response.json());
### upload `FormData`
-`FormData` request bodies work like `fetch`: the multipart boundary and `content-type` header are generated automatically.
+`FormData` request bodies work like `fetch`. By default, `node-wreq` generates a
+`----WebKitFormBoundary...` boundary so the multipart envelope matches browser traffic as well as
+the TLS and HTTP fingerprints do. File parts are encoded by native `wreq` multipart support and
+streamed with backpressure instead of buffering the complete form in memory.
```ts
const formData = new FormData();
@@ -153,6 +161,7 @@ formData.append('upload', new File(['hello'], 'hello.txt', { type: 'text/plain'
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData,
+ // Optional: multipartBoundary: '----my-explicit-boundary',
});
console.log(await response.json());
@@ -206,7 +215,8 @@ readable.pipe(process.stdout);
## 🧩 client  ·  [↑](#contents)
-Use `createClient(...)` when requests share defaults:
+Use `createClient(...)` when requests share defaults. A client also owns reusable native connection
+and TLS-session pools; it is not just a JavaScript defaults wrapper.
- `baseURL`
- browser profile
@@ -250,6 +260,9 @@ const created = await client.post(
);
console.log(created.status);
+
+// Optional for long-lived processes; pooled resources are also released after GC.
+client.close();
```
### extend a client
@@ -291,6 +304,37 @@ Typical profiles include browser families like:
- Opera
- OkHttp
+The current upstream snapshot includes the newest profiles through `chrome_149`, `edge_148`,
+`firefox_151`, `opera_131`, and `safari_26_4`. When `browser` is omitted, `chrome_149` is used.
+
+You can also select a platform explicitly or ask upstream to choose a profile automatically:
+
+```ts
+await fetch('https://example.com', {
+ browser: {
+ profile: 'chrome_149',
+ platform: 'windows',
+ // Optional component switches from upstream's Emulation builder:
+ http2: true,
+ headers: true,
+ },
+});
+
+await fetch('https://example.com', {
+ browser: { mode: 'random' },
+});
+
+await fetch('https://example.com', {
+ // Uses current browser-market-share weights and valid browser/platform pairings.
+ browser: { mode: 'weighted-random' },
+});
+```
+
+Set `browser.http2` to `false` when you want the selected TLS/profile identity without its
+HTTP/2 fingerprint settings. Set `browser.headers` to `false` to omit the profile's default
+headers and header ordering; the top-level `disableDefaultHeaders` option remains a convenient
+request-wide equivalent for the latter.
+
## 🪝 hooks  ·  [↑](#contents)
Hooks are the request pipeline.
@@ -701,6 +745,7 @@ Notes:
- cookies from `cookieJar` are sent during handshake
- duplicate subprotocols are rejected
+- `httpVersion: '1.1' | '2'` explicitly selects the handshake HTTP version
## 🧪 networking / transport knobs  ·  [↑](#contents)
@@ -796,6 +841,7 @@ await fetch('https://example.com', {
browser: 'chrome_137',
tlsOptions: {
greaseEnabled: true,
+ keyShares: ['X25519_MLKEM768', 'X25519', 'P256'],
},
http1Options: {
writev: true,
@@ -813,6 +859,43 @@ Use these only when:
- you are comparing transport behavior
- you want to debug fingerprint mismatches
+Custom TLS/HTTP1/HTTP2 options are overlaid on the selected browser profile. Unspecified profile
+settings remain intact.
+
+### native connection and TLS-session pools
+
+`createClient()` reuses the underlying native `wreq::Client`, including HTTP keep-alive connections
+and TLS sessions. Builder-affecting per-request overrides automatically get an isolated client
+variant so a proxy, DNS, TLS, or browser change cannot reuse an incompatible pool.
+
+```ts
+const client = createClient({
+ baseURL: 'https://api.example.com',
+ poolIdleTimeout: 90_000,
+ poolMaxIdlePerHost: 8,
+ poolMaxSize: 128,
+ tlsSessionCacheCapacity: 8,
+});
+
+await client.get('/account', {
+ // Requests in different groups never share pooled connections.
+ connectionGroup: 'account-session',
+});
+
+const response = await client.get('/health', {
+ // Static form: discard this request's connection after the response.
+ forbidConnectionReuse: true,
+});
+
+// Conditional form: call before consuming the body.
+response.wreq.forbidConnectionReuse();
+
+client.close();
+```
+
+Set `poolIdleTimeout: false` to disable idle expiry. `connectionGroup` accepts a string or a
+non-negative integer.
+
### mTLS and custom CAs
Use `tlsIdentity` for client certificate authentication and `ca` for a custom trust store:
diff --git a/package-lock.json b/package-lock.json
index 3fe6b96..6e2b126 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"@stylistic/eslint-plugin": "^5.10.0",
"@types/node": "^20.19.39",
"@types/ws": "^8.18.1",
+ "lint-staged": "^15.5.2",
"oxfmt": "^0.60.0",
"oxlint": "^1.60.0",
"rimraf": "^6.1.3",
@@ -343,9 +344,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -363,9 +361,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -383,9 +378,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -403,9 +395,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -423,9 +412,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -443,9 +429,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -463,9 +446,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -483,9 +463,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -690,9 +667,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -710,9 +684,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -730,9 +701,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -750,9 +718,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -770,9 +735,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -790,9 +752,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -810,9 +769,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -830,9 +786,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1030,6 +983,48 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
+ "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -1041,16 +1036,92 @@
}
},
"node_modules/brace-expansion": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
- "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
+ "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "^5.0.0",
+ "string-width": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
+ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/cross-spawn": {
@@ -1059,7 +1130,6 @@
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -1075,7 +1145,6 @@
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -1096,6 +1165,26 @@
"license": "MIT",
"peer": true
},
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1300,6 +1389,37 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -1338,6 +1458,19 @@
"node": ">=16.0.0"
}
},
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -1379,6 +1512,32 @@
"license": "ISC",
"peer": true
},
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
@@ -1411,6 +1570,16 @@
"node": ">=10.13.0"
}
},
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -1444,6 +1613,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -1458,13 +1640,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
- "license": "ISC",
- "peer": true
+ "license": "ISC"
},
"node_modules/json-buffer": {
"version": "3.0.1",
@@ -1516,6 +1720,65 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lint-staged": {
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz",
+ "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.4.1",
+ "commander": "^13.1.0",
+ "debug": "^4.4.0",
+ "execa": "^8.0.1",
+ "lilconfig": "^3.1.3",
+ "listr2": "^8.2.5",
+ "micromatch": "^4.0.8",
+ "pidtree": "^0.6.0",
+ "string-argv": "^0.3.2",
+ "yaml": "^2.7.0"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "8.3.3",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz",
+ "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cli-truncate": "^4.0.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -1533,6 +1796,59 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/log-update": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/slice-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
"node_modules/lru-cache": {
"version": "11.3.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
@@ -1543,6 +1859,66 @@
"node": "20 || >=22"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -1574,8 +1950,7 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
@@ -1585,6 +1960,51 @@
"license": "MIT",
"peer": true
},
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -1763,7 +2183,6 @@
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=8"
}
@@ -1798,6 +2217,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pidtree": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz",
+ "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -1820,6 +2252,46 @@
"node": ">=6"
}
},
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/rimraf": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz",
@@ -1846,7 +2318,6 @@
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -1860,11 +2331,97 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=8"
}
},
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.0.0",
+ "is-fullwidth-code-point": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/tinypool": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz",
@@ -1875,6 +2432,19 @@
"node": "^20.0.0 || >=22.0.0"
}
},
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -1923,7 +2493,6 @@
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
- "peer": true,
"dependencies": {
"isexe": "^2.0.0"
},
@@ -1945,6 +2514,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
@@ -1967,6 +2554,22 @@
}
}
},
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index d2f8f71..f1aa757 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"test": "npm run build && node --test dist/test/node-wreq.spec.js",
"lint": "oxlint --deny-warnings src scripts",
"lint:fix": "oxlint --fix src scripts",
+ "lint:staged": "lint-staged",
"lint:rust": "cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings",
"format": "oxfmt --write src scripts",
"format:check": "oxfmt --check src scripts",
@@ -73,6 +74,7 @@
"@stylistic/eslint-plugin": "^5.10.0",
"@types/node": "^20.19.39",
"@types/ws": "^8.18.1",
+ "lint-staged": "^15.5.2",
"oxfmt": "^0.60.0",
"oxlint": "^1.60.0",
"rimraf": "^6.1.3",
@@ -108,5 +110,12 @@
"x86_64-pc-windows-msvc"
]
}
+ },
+ "lint-staged": {
+ "*.{ts,tsx,mts,cts,js,mjs,cjs}": [
+ "oxlint --fix --deny-warnings --no-error-on-unmatched-pattern",
+ "oxfmt --write"
+ ],
+ "rust/**/*.rs": "rustfmt --edition 2021"
}
}
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 6df1744..3857048 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -8,18 +8,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -44,6 +32,12 @@ dependencies = [
"alloc-no-stdlib",
]
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
[[package]]
name = "anyhow"
version = "1.0.100"
@@ -80,6 +74,12 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
[[package]]
name = "bindgen"
version = "0.72.1"
@@ -100,9 +100,9 @@ dependencies = [
[[package]]
name = "bitflags"
-version = "2.9.4"
+version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "block-buffer"
@@ -113,31 +113,6 @@ dependencies = [
"generic-array",
]
-[[package]]
-name = "boring-sys2"
-version = "5.0.0-alpha.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "455d79965f5155dcc88a7abce112c3590883889131b799beda10bf9a813ed669"
-dependencies = [
- "bindgen",
- "cmake",
- "fs_extra",
- "fslock",
-]
-
-[[package]]
-name = "boring2"
-version = "5.0.0-alpha.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "183ccc3854411c035410dcdbffafca62084f3a6c33f013c77e83c025d2a08a28"
-dependencies = [
- "bitflags",
- "boring-sys2",
- "foreign-types",
- "libc",
- "openssl-macros",
-]
-
[[package]]
name = "brotli"
version = "8.0.2"
@@ -159,6 +134,31 @@ dependencies = [
"alloc-stdlib",
]
+[[package]]
+name = "btls"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c5e60b8c8d282c86360cab651ded04ab0335a7b5390c8d34145cbeab8cacf5f"
+dependencies = [
+ "bitflags",
+ "btls-sys",
+ "foreign-types",
+ "libc",
+ "openssl-macros",
+]
+
+[[package]]
+name = "btls-sys"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b1b8638a2e1c38a5ae4efa90ae57e643baec35a30d03fc5b399b893adc4954b"
+dependencies = [
+ "bindgen",
+ "cmake",
+ "fs_extra",
+ "fslock",
+]
+
[[package]]
name = "bumpalo"
version = "3.20.2"
@@ -167,9 +167,9 @@ checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
-version = "1.10.1"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cc"
@@ -198,6 +198,17 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
+
[[package]]
name = "clang-sys"
version = "1.8.1"
@@ -218,6 +229,16 @@ dependencies = [
"cc",
]
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
[[package]]
name = "compression-codecs"
version = "0.4.31"
@@ -273,6 +294,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crc32fast"
version = "1.5.0"
@@ -399,9 +429,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
-version = "1.1.4"
+version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -419,6 +449,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
[[package]]
name = "foreign-types"
version = "0.5.0"
@@ -473,18 +509,18 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-io"
@@ -492,29 +528,40 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+[[package]]
+name = "futures-macro"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "futures-sink"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
[[package]]
name = "futures-task"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
[[package]]
name = "futures-util"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [
"futures-core",
+ "futures-macro",
"futures-sink",
"futures-task",
"pin-project-lite",
- "pin-utils",
"slab",
]
@@ -560,6 +607,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
+ "rand_core 0.10.1",
"wasip2",
"wasip3",
]
@@ -589,19 +637,13 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "hashbrown"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
-
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
@@ -610,12 +652,47 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+[[package]]
+name = "hickory-net"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "hickory-proto 0.26.1",
+ "idna",
+ "ipnet",
+ "jni",
+ "rand 0.10.2",
+ "thiserror 2.0.17",
+ "tinyvec",
+ "tokio",
+ "tracing",
+ "url",
+]
+
[[package]]
name = "hickory-proto"
version = "0.25.2"
@@ -635,7 +712,7 @@ dependencies = [
"idna",
"ipnet",
"once_cell",
- "rand",
+ "rand 0.9.2",
"ring",
"rustls",
"thiserror 2.0.17",
@@ -647,6 +724,26 @@ dependencies = [
"webpki-roots 0.26.11",
]
+[[package]]
+name = "hickory-proto"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643"
+dependencies = [
+ "data-encoding",
+ "idna",
+ "ipnet",
+ "jni",
+ "once_cell",
+ "prefix-trie",
+ "rand 0.10.2",
+ "ring",
+ "thiserror 2.0.17",
+ "tinyvec",
+ "tracing",
+ "url",
+]
+
[[package]]
name = "hickory-resolver"
version = "0.25.2"
@@ -655,12 +752,12 @@ checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
dependencies = [
"cfg-if",
"futures-util",
- "hickory-proto",
+ "hickory-proto 0.25.2",
"ipconfig",
"moka",
"once_cell",
"parking_lot",
- "rand",
+ "rand 0.9.2",
"resolv-conf",
"rustls",
"smallvec",
@@ -671,14 +768,39 @@ dependencies = [
"webpki-roots 0.26.11",
]
+[[package]]
+name = "hickory-resolver"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-net",
+ "hickory-proto 0.26.1",
+ "ipconfig",
+ "ipnet",
+ "jni",
+ "moka",
+ "ndk-context",
+ "once_cell",
+ "parking_lot",
+ "rand 0.10.2",
+ "resolv-conf",
+ "smallvec",
+ "system-configuration",
+ "thiserror 2.0.17",
+ "tokio",
+ "tracing",
+]
+
[[package]]
name = "http"
-version = "1.3.1"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
- "fnv",
"itoa",
]
@@ -707,9 +829,9 @@ dependencies = [
[[package]]
name = "http2"
-version = "0.5.16"
+version = "0.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7349aa548b6a413a7e7146d5d7e3db4565b3d0fbc8ff53c72e46b44b083cf9f"
+checksum = "92d3114be2f413b2e491e686b93a28cda30c355cffc8d091a57f8be4b1342896"
dependencies = [
"atomic-waker",
"bytes",
@@ -718,7 +840,6 @@ dependencies = [
"futures-sink",
"http",
"indexmap",
- "parking_lot",
"slab",
"smallvec",
"tokio",
@@ -867,9 +988,12 @@ dependencies = [
[[package]]
name = "ipnet"
-version = "2.11.0"
+version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+dependencies = [
+ "serde",
+]
[[package]]
name = "itertools"
@@ -886,6 +1010,55 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+[[package]]
+name = "jni"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
+dependencies = [
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys",
+ "log",
+ "simd_cesu8",
+ "thiserror 2.0.17",
+ "walkdir",
+ "windows-link",
+]
+
+[[package]]
+name = "jni-macros"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "simd_cesu8",
+ "syn",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn",
+]
+
[[package]]
name = "jobserver"
version = "0.1.34"
@@ -914,9 +1087,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
-version = "0.2.184"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libloading"
@@ -969,6 +1142,15 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+[[package]]
+name = "lru"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
+dependencies = [
+ "hashbrown 0.17.1",
+]
+
[[package]]
name = "memchr"
version = "2.7.6"
@@ -1035,6 +1217,12 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
[[package]]
name = "neon"
version = "1.1.1"
@@ -1067,13 +1255,14 @@ name = "node-wreq"
version = "0.1.0"
dependencies = [
"anyhow",
- "hickory-resolver",
+ "hickory-resolver 0.25.2",
+ "http-body-util",
"neon",
"serde",
"serde_json",
- "strum",
"thiserror 1.0.69",
"tokio",
+ "tokio-stream",
"url",
"webpki-root-certs",
"wreq",
@@ -1096,6 +1285,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -1148,15 +1346,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
@@ -1194,6 +1386,17 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "prefix-trie"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7"
+dependencies = [
+ "either",
+ "ipnet",
+ "num-traits",
+]
+
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -1241,7 +1444,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
- "rand_core",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.2",
+ "rand_core 0.10.1",
]
[[package]]
@@ -1251,7 +1465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.9.3",
]
[[package]]
@@ -1263,6 +1477,12 @@ dependencies = [
"getrandom 0.3.3",
]
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -1327,6 +1547,15 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
[[package]]
name = "rustls"
version = "0.23.40"
@@ -1375,14 +1604,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
-name = "schnellru"
-version = "0.2.4"
+name = "same-file"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
- "ahash",
- "cfg-if",
- "hashbrown 0.13.2",
+ "winapi-util",
]
[[package]]
@@ -1453,7 +1680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -1478,6 +1705,22 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+[[package]]
+name = "simd_cesu8"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
[[package]]
name = "slab"
version = "0.4.11"
@@ -1506,24 +1749,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-[[package]]
-name = "strum"
-version = "0.27.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
-
-[[package]]
-name = "strum_macros"
-version = "0.27.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "subtle"
version = "2.6.1"
@@ -1686,9 +1911,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.51.1"
+version = "1.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
dependencies = [
"bytes",
"libc",
@@ -1702,12 +1927,12 @@ dependencies = [
]
[[package]]
-name = "tokio-boring2"
-version = "5.0.0-alpha.13"
+name = "tokio-btls"
+version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f81df1210d791f31d72d840de8fbd80b9c3cb324956523048b1413e2bd55756"
+checksum = "2e1fd638ec35427faf3b8f412e0fdd6fae76591d79dba40f38fa667d22bc44dd"
dependencies = [
- "boring2",
+ "btls",
"tokio",
]
@@ -1744,11 +1969,22 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "tokio-stream"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
[[package]]
name = "tokio-tungstenite"
-version = "0.28.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
+checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
dependencies = [
"futures-util",
"log",
@@ -1758,13 +1994,14 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.16"
+version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
+ "libc",
"pin-project-lite",
"tokio",
]
@@ -1843,19 +2080,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
-version = "0.28.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
+checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
- "rand",
+ "rand 0.9.2",
"sha1",
"thiserror 2.0.17",
- "utf-8",
]
[[package]]
@@ -1920,12 +2156,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "utf-8"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -1949,6 +2179,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -2072,9 +2312,9 @@ dependencies = [
[[package]]
name = "webpki-root-certs"
-version = "1.0.3"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e"
+checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
dependencies = [
"rustls-pki-types",
]
@@ -2119,6 +2359,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -2338,20 +2587,17 @@ dependencies = [
[[package]]
name = "wreq"
-version = "6.0.0-rc.28"
+version = "6.0.0-rc.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f79937f6c4df65b3f6f78715b9de2977afe9ee3b3436483c7949a24511e25935"
+checksum = "3f0eba5f5814a94e5f1a99156f187133464e525b66bdbc69a9627d46530af2e1"
dependencies = [
- "ahash",
- "boring2",
- "brotli",
+ "btls",
+ "btls-sys",
"bytes",
"cookie",
"encoding_rs",
- "flate2",
- "futures-channel",
"futures-util",
- "hickory-resolver",
+ "hickory-resolver 0.26.1",
"http",
"http-body",
"http-body-util",
@@ -2359,41 +2605,73 @@ dependencies = [
"httparse",
"ipnet",
"libc",
+ "lru",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
- "schnellru",
"serde",
"serde_json",
- "smallvec",
"socket2",
"sync_wrapper",
"system-configuration",
"tokio",
- "tokio-boring2",
+ "tokio-btls",
"tokio-socks",
"tokio-tungstenite",
+ "tokio-util",
"tower",
"tower-http",
"url",
- "want",
"webpki-root-certs",
"windows-registry",
- "zstd",
+ "wreq-proto",
+ "wreq-rt",
+]
+
+[[package]]
+name = "wreq-proto"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a43942f024bb303f1042c9aa3c87fa1d9149f507c65db6e5220a11ccdb207387"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "http2",
+ "httparse",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "tokio-util",
+ "want",
+]
+
+[[package]]
+name = "wreq-rt"
+version = "0.2.2-rc.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99e9bce67a3fa3dd3f1503f066d86661c9caf399a763d3bd184da7afaf886c8b"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
+ "wreq-proto",
]
[[package]]
name = "wreq-util"
-version = "3.0.0-rc.11"
+version = "3.0.0-rc.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b51558351cf26c9a5daeabf5df5bfab13f1666fa783ad7ddb9a573e04d7fae0a"
+checksum = "1d8d73c75be86fbde675988dbe5cb15f02567025c13f6ffab910361ad1ba6c89"
dependencies = [
+ "brotli",
+ "flate2",
"serde",
- "strum",
- "strum_macros",
"typed-builder",
"wreq",
+ "zstd",
]
[[package]]
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 8e36f5a..beb73c3 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -9,9 +9,9 @@ crate-type = ["cdylib"]
[dependencies]
# HTTP client with browser impersonation
-wreq = { version = "6.0.0-rc.28", default-features = false, features = ["gzip", "brotli", "deflate", "zstd", "socks", "cookies", "json", "charset", "multipart", "hickory-dns", "system-proxy", "webpki-roots", "ws"] }
-wreq-util = { version = "3.0.0-rc.11", features = ["emulation-rand", "emulation-serde"] }
-strum = "0.27.2"
+wreq = { version = "6.0.0-rc.29", default-features = false, features = ["tokio-rt", "gzip", "brotli", "deflate", "zstd", "socks", "cookies", "json", "charset", "multipart", "stream", "hickory-dns", "system-proxy", "webpki-roots", "ws"] }
+wreq-util = { version = "3.0.0-rc.14", features = ["emulation-serde"] }
+http-body-util = "0.1"
# Neon for Node.js bindings
neon = { version = "1.0", default-features = false, features = ["napi-6"] }
@@ -26,6 +26,7 @@ thiserror = "1.0"
# Async runtime (if needed)
tokio = { version = "1.0", features = ["full"] }
+tokio-stream = "0.1"
webpki-root-certs = "1.0.3"
hickory-resolver = { version = "0.25.2", features = ["system-config", "https-ring", "webpki-roots"] }
url = "2.5"
diff --git a/rust/src/emulation/builders.rs b/rust/src/emulation/builders.rs
index 7f1af5f..5d3a29c 100644
--- a/rust/src/emulation/builders.rs
+++ b/rust/src/emulation/builders.rs
@@ -1,246 +1,287 @@
use crate::emulation::parse::{
parse_alpn_protocol, parse_alps_protocol, parse_certificate_compression_algorithm,
- parse_http2_setting_id, parse_pseudo_id, parse_tls_version,
+ parse_http2_setting_id, parse_key_share, parse_pseudo_id, parse_tls_version,
};
use crate::emulation::payload::{
- CustomHttp1Options, CustomHttp2ExperimentalSetting, CustomHttp2Options, CustomHttp2Priority,
- CustomTlsOptions,
+ CustomHttp1Options, CustomHttp2Options, CustomHttp2Priority, CustomTlsOptions,
};
-use anyhow::{anyhow, bail, Result};
+use anyhow::{bail, Result};
use std::collections::HashSet;
use std::time::Duration;
use wreq::{
http1::Http1Options,
http2::{
- ExperimentalSettings, Http2Options, Priorities, Priority, PseudoOrder, Setting, SettingId,
- SettingsOrder, StreamDependency, StreamId,
+ Http2Options, Priorities, Priority, PseudoOrder, SettingsOrder, StreamDependency, StreamId,
},
- tls::{ExtensionType, TlsOptions},
+ tls::{ExtensionType, KeyShare, TlsOptions},
};
-pub fn build_tls_options(options: CustomTlsOptions) -> Result {
- let mut builder = TlsOptions::builder();
+const DEFAULT_KEY_SHARES: &[KeyShare] =
+ &[KeyShare::X25519_MLKEM768, KeyShare::X25519, KeyShare::P256];
+
+pub fn apply_tls_options(mut base: TlsOptions, options: CustomTlsOptions) -> Result {
+ if options.key_shares.is_some() && options.key_shares_limit.is_some() {
+ bail!("Invalid emulation tlsOptions: keyShares and keySharesLimit cannot both be set");
+ }
if let Some(alpn_protocols) = options.alpn_protocols {
- builder = builder.alpn_protocols(
+ base.alpn_protocols = Some(
alpn_protocols
.into_iter()
.map(|protocol| parse_alpn_protocol(&protocol))
- .collect::>>()?,
+ .collect::>>()?
+ .into(),
);
}
if let Some(alps_protocols) = options.alps_protocols {
- builder = builder.alps_protocols(
+ base.alps_protocols = Some(
alps_protocols
.into_iter()
.map(|protocol| parse_alps_protocol(&protocol))
- .collect::>>()?,
+ .collect::>>()?
+ .into(),
);
}
if let Some(value) = options.alps_use_new_codepoint {
- builder = builder.alps_use_new_codepoint(value);
+ base.alps_use_new_codepoint = value;
}
if let Some(value) = options.session_ticket {
- builder = builder.session_ticket(value);
+ base.session_ticket = value;
}
if let Some(value) = options.min_tls_version {
- builder = builder.min_tls_version(Some(parse_tls_version(&value)?));
+ base.min_tls_version = Some(parse_tls_version(&value)?);
}
if let Some(value) = options.max_tls_version {
- builder = builder.max_tls_version(Some(parse_tls_version(&value)?));
+ base.max_tls_version = Some(parse_tls_version(&value)?);
}
if let Some(value) = options.pre_shared_key {
- builder = builder.pre_shared_key(value);
+ base.pre_shared_key = value;
}
if let Some(value) = options.enable_ech_grease {
- builder = builder.enable_ech_grease(value);
+ base.enable_ech_grease = value;
}
if let Some(value) = options.permute_extensions {
- builder = builder.permute_extensions(Some(value));
+ base.permute_extensions = Some(value);
}
if let Some(value) = options.grease_enabled {
- builder = builder.grease_enabled(Some(value));
+ base.grease_enabled = Some(value);
}
if let Some(value) = options.enable_ocsp_stapling {
- builder = builder.enable_ocsp_stapling(value);
+ base.enable_ocsp_stapling = value;
}
if let Some(value) = options.enable_signed_cert_timestamps {
- builder = builder.enable_signed_cert_timestamps(value);
+ base.enable_signed_cert_timestamps = value;
}
if let Some(value) = options.record_size_limit {
- builder = builder.record_size_limit(Some(value));
+ base.record_size_limit = Some(value);
}
if let Some(value) = options.psk_skip_session_ticket {
- builder = builder.psk_skip_session_ticket(value);
+ base.psk_skip_session_ticket = value;
}
if let Some(value) = options.key_shares_limit {
- builder = builder.key_shares_limit(Some(value));
+ if value == 0 {
+ bail!("Invalid emulation tlsOptions.keySharesLimit: must be greater than 0");
+ }
+
+ let mut key_shares = base
+ .key_shares
+ .take()
+ .map(|shares| shares.into_owned())
+ .unwrap_or_else(|| DEFAULT_KEY_SHARES.to_vec());
+ key_shares.truncate(usize::from(value));
+ base.key_shares = Some(key_shares.into());
+ }
+ if let Some(key_shares) = options.key_shares {
+ let key_shares = key_shares
+ .into_iter()
+ .map(|key_share| parse_key_share(&key_share))
+ .collect::>>()?;
+
+ if key_shares.is_empty() {
+ bail!("Invalid emulation tlsOptions.keyShares: must not be empty");
+ }
+
+ base.key_shares = Some(key_shares.into());
}
if let Some(value) = options.psk_dhe_ke {
- builder = builder.psk_dhe_ke(value);
+ base.psk_dhe_ke = value;
}
if let Some(value) = options.renegotiation {
- builder = builder.renegotiation(value);
+ base.renegotiation = value;
}
if let Some(value) = options.delegated_credentials {
- builder = builder.delegated_credentials(value);
+ base.delegated_credentials = Some(value.into());
}
if let Some(value) = options.curves_list {
- builder = builder.curves_list(value);
+ base.curves_list = Some(value.into());
}
if let Some(value) = options.cipher_list {
- builder = builder.cipher_list(value);
+ base.cipher_list = Some(value.into());
}
if let Some(value) = options.sigalgs_list {
- builder = builder.sigalgs_list(value);
+ base.sigalgs_list = Some(value.into());
}
if let Some(value) = options.certificate_compression_algorithms {
- builder = builder.certificate_compression_algorithms(
+ base.certificate_compressors = Some(
value
.into_iter()
.map(|algorithm| parse_certificate_compression_algorithm(&algorithm))
- .collect::>>()?,
+ .collect::>>()?
+ .into(),
);
}
if let Some(value) = options.extension_permutation {
- builder = builder.extension_permutation(
+ base.extension_permutation = Some(
value
.into_iter()
.map(ExtensionType::from)
- .collect::>(),
+ .collect::>()
+ .into(),
);
}
if let Some(value) = options.aes_hw_override {
- builder = builder.aes_hw_override(Some(value));
+ base.aes_hw_override = Some(value);
}
if let Some(value) = options.preserve_tls13_cipher_list {
- builder = builder.preserve_tls13_cipher_list(Some(value));
+ base.preserve_tls13_cipher_list = Some(value);
}
if let Some(value) = options.random_aes_hw_override {
- builder = builder.random_aes_hw_override(value);
+ base.random_aes_hw_override = value;
}
- Ok(builder.build())
+ Ok(base)
}
-pub fn build_http1_options(options: CustomHttp1Options) -> Result {
- let mut builder = Http1Options::builder();
-
+pub fn apply_http1_options(
+ mut base: Http1Options,
+ options: CustomHttp1Options,
+) -> Result {
if let Some(value) = options.http09_responses {
- builder = builder.http09_responses(value);
+ base.h09_responses = value;
}
if let Some(value) = options.writev {
- builder = builder.writev(Some(value));
+ base.h1_writev = Some(value);
}
if let Some(value) = options.max_headers {
- builder = builder.max_headers(value);
+ base.h1_max_headers = Some(value);
}
if let Some(value) = options.read_buf_exact_size {
- builder = builder.read_buf_exact_size(Some(value));
+ base.h1_read_buf_exact_size = Some(value);
+ base.h1_max_buf_size = None;
}
if let Some(value) = options.max_buf_size {
if value < 8192 {
bail!("Invalid emulation http1Options.maxBufSize: must be at least 8192");
}
- builder = builder.max_buf_size(value);
+ base.h1_max_buf_size = Some(value);
+ base.h1_read_buf_exact_size = None;
}
if options.read_buf_exact_size.is_some() && options.max_buf_size.is_some() {
bail!("Invalid emulation http1Options: readBufExactSize and maxBufSize cannot both be set");
}
if let Some(value) = options.ignore_invalid_headers_in_responses {
- builder = builder.ignore_invalid_headers_in_responses(value);
+ base.ignore_invalid_headers_in_responses = value;
}
if let Some(value) = options.allow_spaces_after_header_name_in_responses {
- builder = builder.allow_spaces_after_header_name_in_responses(value);
+ base.allow_spaces_after_header_name_in_responses = value;
}
if let Some(value) = options.allow_obsolete_multiline_headers_in_responses {
- builder = builder.allow_obsolete_multiline_headers_in_responses(value);
+ base.allow_obsolete_multiline_headers_in_responses = value;
}
- Ok(builder.build())
+ Ok(base)
}
-pub fn build_http2_options(options: CustomHttp2Options) -> Result {
- let mut builder = Http2Options::builder();
-
+pub fn apply_http2_options(
+ mut base: Http2Options,
+ options: CustomHttp2Options,
+) -> Result {
if let Some(value) = options.adaptive_window {
- builder = builder.adaptive_window(value);
+ base.adaptive_window = value;
}
if let Some(value) = options.initial_stream_id {
- builder = builder.initial_stream_id(Some(value));
+ base.initial_stream_id = Some(value);
}
if let Some(value) = options.initial_connection_window_size {
- builder = builder.initial_connection_window_size(Some(value));
+ base.initial_conn_window_size = value;
+ base.adaptive_window = false;
}
if let Some(value) = options.initial_window_size {
- builder = builder.initial_window_size(Some(value));
+ base.initial_window_size = value;
+ base.adaptive_window = false;
}
if let Some(value) = options.initial_max_send_streams {
- builder = builder.initial_max_send_streams(Some(value));
+ base.initial_max_send_streams = value;
}
if let Some(value) = options.max_frame_size {
- builder = builder.max_frame_size(Some(value));
+ base.max_frame_size = Some(value);
}
if let Some(value) = options.keep_alive_interval {
- builder = builder.keep_alive_interval(Some(Duration::from_millis(value)));
+ base.keep_alive_interval = Some(Duration::from_millis(value));
}
if let Some(value) = options.keep_alive_timeout {
- builder = builder.keep_alive_timeout(Duration::from_millis(value));
+ base.keep_alive_timeout = Duration::from_millis(value);
}
if let Some(value) = options.keep_alive_while_idle {
- builder = builder.keep_alive_while_idle(value);
+ base.keep_alive_while_idle = value;
}
if let Some(value) = options.max_concurrent_reset_streams {
- builder = builder.max_concurrent_reset_streams(value);
+ base.max_concurrent_reset_streams = Some(value);
}
if let Some(value) = options.max_send_buffer_size {
- builder = builder.max_send_buf_size(value);
+ if value > u32::MAX as usize {
+ bail!("Invalid emulation http2Options.maxSendBufferSize: exceeds u32::MAX");
+ }
+ base.max_send_buffer_size = value;
}
if let Some(value) = options.max_concurrent_streams {
- builder = builder.max_concurrent_streams(Some(value));
+ base.max_concurrent_streams = Some(value);
}
if let Some(value) = options.max_header_list_size {
- builder = builder.max_header_list_size(value);
+ base.max_header_list_size = Some(value);
}
if let Some(value) = options.max_pending_accept_reset_streams {
- builder = builder.max_pending_accept_reset_streams(Some(value));
+ base.max_pending_accept_reset_streams = Some(value);
}
if let Some(value) = options.enable_push {
- builder = builder.enable_push(value);
+ base.enable_push = Some(value);
}
if let Some(value) = options.header_table_size {
- builder = builder.header_table_size(Some(value));
+ base.header_table_size = Some(value);
}
if let Some(value) = options.enable_connect_protocol {
- builder = builder.enable_connect_protocol(value);
+ base.enable_connect_protocol = Some(value);
}
if let Some(value) = options.no_rfc7540_priorities {
- builder = builder.no_rfc7540_priorities(value);
+ base.no_rfc7540_priorities = Some(value);
}
if let Some(settings_order) = options.settings_order {
- builder = builder.settings_order(Some(build_settings_order(settings_order)?));
+ base.settings_order = Some(build_settings_order(settings_order)?);
}
if let Some(pseudo_order) = options.headers_pseudo_order {
- builder = builder.headers_pseudo_order(Some(build_pseudo_order(pseudo_order)?));
+ base.headers_pseudo_order = Some(build_pseudo_order(pseudo_order)?);
}
if let Some(dep) = options.headers_stream_dependency {
- builder = builder.headers_stream_dependency(Some(StreamDependency::new(
+ base.headers_stream_dependency = Some(StreamDependency::new(
StreamId::from(dep.dependency_id),
dep.weight,
dep.exclusive,
- )));
+ ));
}
if let Some(priorities) = options.priorities {
- builder = builder.priorities(Some(build_priorities(priorities)?));
+ base.priorities = Some(build_priorities(priorities)?);
}
if let Some(experimental_settings) = options.experimental_settings {
- builder = builder
- .experimental_settings(Some(build_experimental_settings(experimental_settings)?));
+ if !experimental_settings.is_empty() {
+ bail!(
+ "Unsupported emulation http2Options.experimentalSettings: wreq 6.0.0-rc.29 no longer exposes custom HTTP/2 settings"
+ );
+ }
}
- Ok(builder.build())
+ Ok(base)
}
fn build_pseudo_order(pseudo_order: Vec) -> Result {
@@ -305,42 +346,42 @@ fn build_priorities(priorities: Vec) -> Result
Ok(builder.build())
}
-fn build_experimental_settings(
- experimental_settings: Vec,
-) -> Result {
- let mut builder = ExperimentalSettings::builder();
- let mut seen_ids = HashSet::with_capacity(experimental_settings.len());
- let max_id = 15u16;
+#[cfg(test)]
+mod tests {
+ use super::*;
- for setting in experimental_settings {
- if setting.id == 0 || setting.id > max_id {
- bail!(
- "Invalid emulation http2Options.experimentalSettings entry: id must be between 1 and {}",
- max_id
- );
- }
- if !matches!(SettingId::from(setting.id), SettingId::Unknown(_)) {
- bail!(
- "Invalid emulation http2Options.experimentalSettings entry: {} is a standard HTTP/2 setting id",
- setting.id
- );
- }
- if !seen_ids.insert(setting.id) {
- bail!(
- "Duplicate emulation http2Options.experimentalSettings id: {}",
- setting.id
- );
- }
+ #[test]
+ fn tls_overrides_preserve_profile_defaults_and_limit_profile_key_shares() {
+ let mut base = TlsOptions::default();
+ base.session_ticket = false;
+ base.key_shares = Some(vec![KeyShare::X25519, KeyShare::P256].into());
+ let custom = CustomTlsOptions {
+ grease_enabled: Some(true),
+ key_shares_limit: Some(1),
+ ..Default::default()
+ };
- let setting =
- Setting::from_id(SettingId::Unknown(setting.id), setting.value).ok_or_else(|| {
- anyhow!(
- "Invalid emulation http2Options.experimentalSettings id: {}",
- setting.id
- )
- })?;
- builder = builder.push(setting);
+ let merged = apply_tls_options(base, custom).expect("TLS options should merge");
+
+ assert!(!merged.session_ticket);
+ assert_eq!(merged.grease_enabled, Some(true));
+ assert_eq!(
+ merged.key_shares.as_deref(),
+ Some([KeyShare::X25519].as_slice())
+ );
}
- Ok(builder.build())
+ #[test]
+ fn explicit_key_shares_and_limit_are_mutually_exclusive() {
+ let custom = CustomTlsOptions {
+ key_shares: Some(vec!["X25519".to_string()]),
+ key_shares_limit: Some(1),
+ ..Default::default()
+ };
+
+ let error = apply_tls_options(TlsOptions::default(), custom)
+ .expect_err("conflicting key-share options should fail");
+
+ assert!(error.to_string().contains("cannot both be set"));
+ }
}
diff --git a/rust/src/emulation/parse.rs b/rust/src/emulation/parse.rs
index b72fd1d..5d648ea 100644
--- a/rust/src/emulation/parse.rs
+++ b/rust/src/emulation/parse.rs
@@ -1,8 +1,9 @@
use anyhow::{bail, Result};
use wreq::{
http2::{PseudoId, SettingId},
- tls::{AlpnProtocol, AlpsProtocol, CertificateCompressionAlgorithm, TlsVersion},
+ tls::{compress::CertificateCompressor, AlpnProtocol, AlpsProtocol, KeyShare, TlsVersion},
};
+use wreq_util::emulate::compress::{BrotliCompressor, ZlibCompressor, ZstdCompressor};
pub fn parse_tls_version(value: &str) -> Result {
match value {
@@ -34,15 +35,31 @@ pub fn parse_alps_protocol(value: &str) -> Result {
pub fn parse_certificate_compression_algorithm(
value: &str,
-) -> Result {
+) -> Result<&'static dyn CertificateCompressor> {
match value {
- "zlib" => Ok(CertificateCompressionAlgorithm::ZLIB),
- "brotli" => Ok(CertificateCompressionAlgorithm::BROTLI),
- "zstd" => Ok(CertificateCompressionAlgorithm::ZSTD),
+ "zlib" => Ok(&ZlibCompressor),
+ "brotli" => Ok(&BrotliCompressor),
+ "zstd" => Ok(&ZstdCompressor),
other => bail!("Invalid certificate compression algorithm: {other}"),
}
}
+pub fn parse_key_share(value: &str) -> Result {
+ match value {
+ "P256" => Ok(KeyShare::P256),
+ "P384" => Ok(KeyShare::P384),
+ "P521" => Ok(KeyShare::P521),
+ "X25519" => Ok(KeyShare::X25519),
+ "X25519_MLKEM768" => Ok(KeyShare::X25519_MLKEM768),
+ "X25519_KYBER768_DRAFT00" => Ok(KeyShare::X25519_KYBER768_DRAFT00),
+ "P256_KYBER768_DRAFT00" => Ok(KeyShare::P256_KYBER768_DRAFT00),
+ "MLKEM1024" => Ok(KeyShare::MLKEM1024),
+ "FFDHE2048" => Ok(KeyShare::FFDHE2048),
+ "FFDHE3072" => Ok(KeyShare::FFDHE3072),
+ other => bail!("Invalid TLS key share: {other}"),
+ }
+}
+
pub fn parse_pseudo_id(value: &str) -> Result {
match value {
"Method" => Ok(PseudoId::Method),
diff --git a/rust/src/emulation/payload.rs b/rust/src/emulation/payload.rs
index 1029def..5f48811 100644
--- a/rust/src/emulation/payload.rs
+++ b/rust/src/emulation/payload.rs
@@ -45,6 +45,8 @@ pub struct CustomTlsOptions {
#[serde(default)]
pub key_shares_limit: Option,
#[serde(default)]
+ pub key_shares: Option>,
+ #[serde(default)]
pub psk_dhe_ke: Option,
#[serde(default)]
pub renegotiation: Option,
@@ -137,7 +139,7 @@ pub struct CustomHttp2Options {
#[serde(default)]
pub priorities: Option>,
#[serde(default)]
- pub experimental_settings: Option>,
+ pub experimental_settings: Option>,
}
#[derive(Debug, Deserialize)]
@@ -155,9 +157,3 @@ pub struct CustomHttp2StreamDependency {
#[serde(default)]
pub exclusive: bool,
}
-
-#[derive(Debug, Deserialize)]
-pub struct CustomHttp2ExperimentalSetting {
- pub id: u16,
- pub value: u32,
-}
diff --git a/rust/src/emulation/resolve.rs b/rust/src/emulation/resolve.rs
index 3e57ca7..c5bb38d 100644
--- a/rust/src/emulation/resolve.rs
+++ b/rust/src/emulation/resolve.rs
@@ -1,14 +1,35 @@
-use crate::emulation::builders::{build_http1_options, build_http2_options, build_tls_options};
+use crate::emulation::builders::{apply_http1_options, apply_http2_options, apply_tls_options};
use crate::emulation::payload::CustomEmulationPayload;
use anyhow::{Context, Result};
-use wreq::{Emulation as WreqEmulation, EmulationFactory};
-use wreq_util::Emulation as BrowserEmulation;
+use wreq::{Emulation as WreqEmulation, IntoEmulation};
+use wreq_util::{Emulation as BrowserEmulation, Platform, Profile};
pub fn resolve_emulation(
- browser: BrowserEmulation,
+ browser: Profile,
+ mode: &str,
+ platform: Option<&str>,
+ http2: Option,
+ headers: Option,
emulation_json: Option<&str>,
) -> Result {
- let mut emulation = browser.emulation();
+ let browser_emulation = match mode {
+ "fixed" => BrowserEmulation::builder()
+ .profile(browser)
+ .platform(parse_platform(platform)?)
+ .build(),
+ "random" => BrowserEmulation::random(),
+ "weighted-random" => BrowserEmulation::weighted_random(),
+ other => anyhow::bail!("Invalid browser emulation mode: {other}"),
+ };
+ let mut emulation = browser_emulation.into_emulation();
+
+ if http2 == Some(false) {
+ emulation.http2_options = None;
+ }
+ if headers == Some(false) {
+ emulation.headers.clear();
+ emulation.orig_headers = Default::default();
+ }
if let Some(emulation_json) = emulation_json {
let payload = parse_payload(emulation_json)?;
@@ -24,16 +45,53 @@ fn parse_payload(emulation_json: &str) -> Result {
fn apply_payload(emulation: &mut WreqEmulation, payload: CustomEmulationPayload) -> Result<()> {
if let Some(tls_options) = payload.tls_options {
- *emulation.tls_options_mut() = Some(build_tls_options(tls_options)?);
+ let base = emulation.tls_options.take().unwrap_or_default();
+ emulation.tls_options = Some(apply_tls_options(base, tls_options)?);
}
if let Some(http1_options) = payload.http1_options {
- *emulation.http1_options_mut() = Some(build_http1_options(http1_options)?);
+ let base = emulation.http1_options.take().unwrap_or_default();
+ emulation.http1_options = Some(apply_http1_options(base, http1_options)?);
}
if let Some(http2_options) = payload.http2_options {
- *emulation.http2_options_mut() = Some(build_http2_options(http2_options)?);
+ let base = emulation.http2_options.take().unwrap_or_default();
+ emulation.http2_options = Some(apply_http2_options(base, http2_options)?);
}
Ok(())
}
+
+fn parse_platform(platform: Option<&str>) -> Result {
+ match platform.unwrap_or("macos") {
+ "windows" => Ok(Platform::Windows),
+ "macos" => Ok(Platform::MacOS),
+ "linux" => Ok(Platform::Linux),
+ "android" => Ok(Platform::Android),
+ "ios" => Ok(Platform::IOS),
+ other => anyhow::bail!("Invalid browser platform: {other}"),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn can_disable_profile_http2_and_headers() {
+ let emulation = resolve_emulation(
+ Profile::Chrome149,
+ "fixed",
+ Some("linux"),
+ Some(false),
+ Some(false),
+ None,
+ )
+ .expect("emulation should resolve");
+
+ assert!(emulation.http2_options.is_none());
+ assert!(emulation.headers.is_empty());
+ assert!(emulation.orig_headers.is_empty());
+ assert!(emulation.tls_options.is_some());
+ }
+}
diff --git a/rust/src/napi/body.rs b/rust/src/napi/body.rs
index ddc5ae7..dea151f 100644
--- a/rust/src/napi/body.rs
+++ b/rust/src/napi/body.rs
@@ -1,4 +1,4 @@
-use crate::store::body_store::{cancel_body, read_body_chunk};
+use crate::store::body_store::{cancel_body, forbid_body_recycle, read_body_chunk};
use neon::prelude::*;
use neon::types::JsBuffer;
@@ -37,8 +37,18 @@ fn cancel_body_js(mut cx: FunctionContext) -> JsResult {
Ok(cx.boolean(cancel_body(handle)))
}
+fn forbid_body_recycle_js(mut cx: FunctionContext) -> JsResult {
+ let handle = cx.argument::(0)?.value(&mut cx) as u64;
+
+ match forbid_body_recycle(handle) {
+ Ok(value) => Ok(cx.boolean(value)),
+ Err(error) => cx.throw_error(format!("{:#}", error)),
+ }
+}
+
pub fn register(cx: &mut ModuleContext) -> NeonResult<()> {
cx.export_function("readBodyChunk", read_body_chunk_js)?;
cx.export_function("cancelBody", cancel_body_js)?;
+ cx.export_function("forbidBodyRecycle", forbid_body_recycle_js)?;
Ok(())
}
diff --git a/rust/src/napi/convert.rs b/rust/src/napi/convert.rs
index 279bdeb..b4ad945 100644
--- a/rust/src/napi/convert.rs
+++ b/rust/src/napi/convert.rs
@@ -1,8 +1,10 @@
use crate::emulation::resolve_emulation;
use crate::napi::profiles::parse_browser_emulation;
+use crate::store::upload_store::take_upload_receiver;
use crate::transport::types::{
- CertificateAuthorityOptions, DnsOptions, LocalBindOptions, RequestOptions, Response,
- TlsDangerOptions, TlsDebugOptions, TlsIdentityOptions, TlsKeylogOptions,
+ CertificateAuthorityOptions, ConnectionGroup, DnsOptions, LocalBindOptions,
+ MultipartBodyOptions, MultipartPartOptions, PoolIdleTimeout, RequestBody, RequestOptions,
+ Response, TlsDangerOptions, TlsDebugOptions, TlsIdentityOptions, TlsKeylogOptions,
WebSocketConnectOptions, WebSocketConnection,
};
use neon::prelude::*;
@@ -38,6 +40,107 @@ fn js_value_to_positive_usize(
Ok(value.ceil() as usize)
}
+fn js_value_to_non_negative_usize(
+ cx: &mut FunctionContext,
+ value: Handle,
+ name: &str,
+) -> NeonResult {
+ let value = value.downcast::(cx).or_throw(cx)?.value(cx);
+
+ if !value.is_finite() || value < 0.0 || value.fract() != 0.0 {
+ return cx.throw_type_error(format!("{name} must be a finite non-negative integer"));
+ }
+ if value > usize::MAX as f64 {
+ return cx.throw_type_error(format!("{name} exceeds the supported range"));
+ }
+
+ Ok(value as usize)
+}
+
+fn js_object_to_client_identity(
+ cx: &mut FunctionContext,
+ obj: Handle,
+) -> NeonResult<(Option, Option)> {
+ let client_id = obj
+ .get_opt(cx, "clientId")?
+ .map(|value: Handle| {
+ let value = value.downcast::(cx).or_throw(cx)?.value(cx);
+
+ if !value.is_finite()
+ || value.fract() != 0.0
+ || !(0.0..=9_007_199_254_740_991.0).contains(&value)
+ {
+ return cx.throw_type_error("clientId must be a non-negative safe integer");
+ }
+
+ Ok(value as u64)
+ })
+ .transpose()?;
+ let cache_key = obj
+ .get_opt(cx, "clientCacheKey")?
+ .map(|value: Handle| value.downcast::(cx).or_throw(cx))
+ .transpose()?
+ .map(|value| value.value(cx));
+
+ if client_id.is_some() != cache_key.is_some() {
+ return cx.throw_type_error("clientId and clientCacheKey must be provided together");
+ }
+
+ Ok((client_id, cache_key))
+}
+
+fn js_object_to_pool_idle_timeout(
+ cx: &mut FunctionContext,
+ obj: Handle,
+) -> NeonResult