diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0021e2d86..de3cf1a34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [16, 18, 20, 22, 24] + version: [18, 20, 22, 24] steps: - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [16, 18, 20, 22, 24] + version: [18, 20, 22, 24] steps: - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -116,7 +116,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [16, 18, 20, 22, 24] + version: [18, 20, 22, 24] steps: - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/README.md b/README.md index 808fe3f39..8f89d27e0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,10 @@ for a backend written in Node.js. You can read more on the [Descope Website](htt ## Requirements -The SDK supports Node version 16 and above. +The SDK supports Node version 18 and above. + +The SDK uses the runtime's native `fetch` and requires no Node built-in modules, so it also runs +on Cloudflare Workers and other edge runtimes. ## Installing the SDK diff --git a/lib/fetch-polyfill.test.ts b/lib/fetch-polyfill.test.ts new file mode 100644 index 000000000..a0b71c133 --- /dev/null +++ b/lib/fetch-polyfill.test.ts @@ -0,0 +1,21 @@ +// the dist assertion requires to run `npm run build` before running the test + +import { readFileSync } from 'fs'; +import polyfillFetch from './fetch-polyfill'; + +describe('fetch-polyfill', () => { + it('should delegate to the runtime native fetch', async () => { + const res = { ok: true }; + const spy = jest.spyOn(globalThis, 'fetch').mockResolvedValue(res as Response); + + await expect(polyfillFetch('https://example.com', { method: 'POST' })).resolves.toBe(res); + expect(spy).toHaveBeenCalledWith('https://example.com', { method: 'POST' }); + }); + + it('should not bundle node-only http clients into the build', () => { + const dist = readFileSync('./dist/index.esm.js', 'utf-8'); + ['cross-fetch', 'node-fetch', 'node:http', 'node:https'].forEach((specifier) => { + expect(dist).not.toContain(specifier); + }); + }); +}); diff --git a/lib/fetch-polyfill.ts b/lib/fetch-polyfill.ts index 2c7c2e5fe..173998542 100644 --- a/lib/fetch-polyfill.ts +++ b/lib/fetch-polyfill.ts @@ -1,31 +1,7 @@ -import { fetch as crossFetch, Headers } from 'cross-fetch'; - -globalThis.Headers ??= Headers; - -const highWaterMarkMb = 1024 * 1024 * 30; // 30MB - -// we are increasing the response buffer size due to an issue where node-fetch hangs when response is too big -const patchedFetch = (...args: Parameters) => { - // we can get Request on the first arg, or RequestInfo on the second arg - // we want to make sure we are setting the "highWaterMark" so we are doing it on both args - args.forEach((arg) => { - // Updated to only apply highWaterMark to objects, as it can't be applied to strings (it breaks it) - if (arg && typeof arg === 'object') { - // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-unused-expressions - (arg as any).highWaterMark ??= highWaterMarkMb; - } - }); - - return crossFetch(...args); -}; - -// node-fetch@2 (bundled by cross-fetch) throws a false ERR_STREAM_PREMATURE_CLOSE on -// keep-alive responses on Node >= 22.23.0 / 24.17.0 (nodejs/node#63989, the CVE-2026-48931 -// http.Agent fix). Node's built-in fetch (undici, Node >= 18) is unaffected, so prefer it -// when present and fall back to cross-fetch (node-fetch) only on older runtimes. -const polyfillFetch = - typeof globalThis.fetch === 'function' - ? (...args: Parameters) => globalThis.fetch(...args) - : patchedFetch; +// Native fetch only (Node >= 18, browsers, Cloudflare Workers and other edge runtimes). +// Bundling a Node-based polyfill (cross-fetch/node-fetch) pulls `http`/`https` into edge +// builds, where unenv stubs them with functions that throw on call. +// Bound through a wrapper so undici's fetch keeps its correct `this`. +const polyfillFetch = (...args: Parameters) => globalThis.fetch(...args); export default polyfillFetch as unknown as typeof fetch; diff --git a/lib/index.ts b/lib/index.ts index bbf27cf85..fdf5cb631 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -52,7 +52,7 @@ const nodeSdk = ({ }: NodeSdkArgs) => { const nodeHeaders = { 'x-descope-sdk-name': 'nodejs', - 'x-descope-sdk-node-version': process?.versions?.node || '', + 'x-descope-sdk-node-version': globalThis.process?.versions?.node || '', 'x-descope-sdk-version': BUILD_VERSION, }; diff --git a/package-lock.json b/package-lock.json index 7b8faba7f..4e12f2eb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@descope/core-js-sdk": "^2.66.0", - "cross-fetch": "^4.0.0", "jose": "5.2.2", "tslib": "^2.0.0" }, @@ -62,7 +61,7 @@ "typescript": "^4.6.4" }, "engines": { - "node": ">= 16.0.0" + "node": ">= 18.0.0" } }, "node_modules/@ampproject/remapping": { @@ -5194,14 +5193,6 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -10444,25 +10435,6 @@ "node": ">=18.20.0 <20 || >=20.12.1" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12759,11 +12731,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-jest": { "version": "29.0.5", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", @@ -13157,20 +13124,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 6dd398133..03a69be4a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "url": "git://github.com/descope/node-sdk.git" }, "engines": { - "node": ">= 16.0.0" + "node": ">= 18.0.0" }, "scripts": { "build": "rimraf dist && rollup -c", @@ -103,7 +103,6 @@ }, "dependencies": { "@descope/core-js-sdk": "^2.66.0", - "cross-fetch": "^4.0.0", "jose": "5.2.2", "tslib": "^2.0.0" }