From ad5b591d1ba4eeefabb77219f910b3e8c0465d40 Mon Sep 17 00:00:00 2001 From: "Dimitri B." Date: Mon, 11 Mar 2024 12:21:53 +0100 Subject: [PATCH 1/4] :factory: Add BasicAuth addon Implement BasicAuth addon that adds following behaviors: - allows setting credentials via the `.basicAuth()` method - parses URLs and extracts credentials from them, converting them to an Authorization header and removing them from the URL which would otherwise throw an error when passed to native fetch() - correctly parses usernames/passwords that contain non-latin characters (see https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem) --- README.md | 19 +++++++++ rollup.config.js | 2 +- src/addons/basicAuth.ts | 80 +++++++++++++++++++++++++++++++++++++ src/addons/index.ts | 2 + src/config.ts | 4 +- src/index.all.ts | 1 + test/browser/wretch.spec.js | 51 +++++++++++++++++------ test/deno/wretch_test.ts | 54 +++++++++++++++++++------ test/mock.js | 2 +- test/node/wretch.spec.ts | 58 ++++++++++++++++++++------- test/tsconfig.json | 4 +- 11 files changed, 231 insertions(+), 46 deletions(-) create mode 100644 src/addons/basicAuth.ts diff --git a/README.md b/README.md index 7e3d5b9..299c624 100644 --- a/README.md +++ b/README.md @@ -607,6 +607,25 @@ wretch("...").addon(AbortAddon()).get().setTimeout(1000).json(_ => ) ``` +### [BasicAuth 🔗](https://elbywan.github.io/wretch/api/modules/addons_basicAuth.BasicAuthAddon.html) + +Adds the ability to set the `Authorization` header for the [basic authentication scheme](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme) without the need to manually encode the username/password. + +Also, allows using URLs with `wretch` that contain credentials, which would otherwise throw an error. + +```js +import BasicAuthAddon from "wretch/addons/basicAuth" + +const user = "user" +const pass = "pass" + +// Automatically sets the Authorization header to "Basic " + +wretch("...").addon(BasicAuthAddon).basicAuth(user, pass).get() + +// Allows using URLs with credentials in them +wretch(`https://${user}:${pass}@...`).addon(BasicAuthAddon).get() +``` + ### [Progress 🔗](https://elbywan.github.io/wretch/api/modules/addons_progress.html) Adds the ability to monitor progress when downloading a response. diff --git a/rollup.config.js b/rollup.config.js index 805fc95..73be4d2 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,7 +2,7 @@ import typescript from '@rollup/plugin-typescript'; import terser from '@rollup/plugin-terser'; import { nodeResolve } from '@rollup/plugin-node-resolve'; -const addons = ["abort", "formData", "formUrl", "perfs", "queryString", "progress"] +const addons = ["abort", "basicAuth", "formData", "formUrl", "perfs", "queryString", "progress"] const middlewares = ["dedupe", "delay", "retry", "throttlingCache"] const common = { diff --git a/src/addons/basicAuth.ts b/src/addons/basicAuth.ts new file mode 100644 index 0000000..4609616 --- /dev/null +++ b/src/addons/basicAuth.ts @@ -0,0 +1,80 @@ +import type { Config, ConfiguredMiddleware, Wretch, WretchAddon } from "../types.js" + +function utf8ToBase64( + input: string, + config: Config, + textEncoder = config.polyfill("TextEncoder", true, true) as TextEncoder, +) { + const utf8Bytes = textEncoder.encode(input) + return btoa(String.fromCharCode(...utf8Bytes)) +} + +export interface BasicAuthAddon { + /** + * Sets the `Authorization` header to `Basic ` + . + * Additionally, allows using URLs with credentials in them. + * + * ```js + * const user = "user" + * const pass = "pass" + * + * // Automatically sets the Authorization header to "Basic " + + * wretch("...").addon(BasicAuthAddon).basicAuth(user, pass).get() + * + * // Allows using URLs with credentials in them + * wretch(`https://${user}:${pass}@...`).addon(BasicAuthAddon).get() + * ``` + * + * @param input - The credentials to use for the basic auth. + */ + basicAuth( + this: T & Wretch, + username: string, + password: string + ): this +} + +const makeBasicAuthMiddleware: (config: Config) => ConfiguredMiddleware = config => next => (url, opts) => { + const _URL = config.polyfill("URL") as typeof URL + const parsedUrl = _URL.canParse(url) ? new _URL(url) : null + + if (parsedUrl?.username || parsedUrl?.password) { + const basicAuthBase64 = utf8ToBase64( + `${decodeURIComponent(parsedUrl.username)}:${decodeURIComponent(parsedUrl.password)}`, + config + ) + opts.headers = { + ...opts.headers, + Authorization: `Basic ${basicAuthBase64}`, + } + parsedUrl.username = "" + parsedUrl.password = "" + url = parsedUrl.toString() + } + + return next(url, opts) +} + + +/** + * Adds the ability to use basic auth with the `Authorization` header. + * + * ```js + * import BasicAuthAddon from "wretch/addons/basicAuth" + * + * wretch().addon(BasicAuthAddon) + * ``` + */ +const basicAuth: WretchAddon = { + beforeRequest(wretch) { + return wretch.middlewares([makeBasicAuthMiddleware(wretch._config)]) + }, + wretch: { + basicAuth(username, password) { + const basicAuthBase64 = utf8ToBase64(`${username}:${password}`, this._config) + return this.auth(`Basic ${basicAuthBase64}`) + }, + }, +} + +export default basicAuth diff --git a/src/addons/index.ts b/src/addons/index.ts index 2223fda..4cfa537 100644 --- a/src/addons/index.ts +++ b/src/addons/index.ts @@ -1,5 +1,7 @@ export { default as abortAddon } from "./abort.js" export type { AbortWretch, AbortResolver } from "./abort.js" +export { default as basicAuthAddon } from "./basicAuth.js" +export type { BasicAuthAddon } from "./basicAuth.js" export { default as formDataAddon } from "./formData.js" export type { FormDataAddon } from "./formData.js" export { default as formUrlAddon } from "./formUrl.js" diff --git a/src/config.ts b/src/config.ts index f134c39..dcf618b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,10 +12,12 @@ const config: Config = { polyfills: { // fetch: null, // FormData: null, + // URL: null, // URLSearchParams: null, // performance: null, // PerformanceObserver: null, - // AbortController: null + // AbortController: null, + // TextEncoder: null }, polyfill(p: string, doThrow: boolean = true, instance: boolean = false, ...args: any[]) { const res = this.polyfills[p] || diff --git a/src/index.all.ts b/src/index.all.ts index aca6827..d4694fe 100644 --- a/src/index.all.ts +++ b/src/index.all.ts @@ -6,6 +6,7 @@ import { WretchError } from "./resolver.js" function factory(_url = "", _options = {}) { return { ...core, _url, _options } .addon(Addons.abortAddon()) + .addon(Addons.basicAuthAddon) .addon(Addons.formDataAddon) .addon(Addons.formUrlAddon) .addon(Addons.perfsAddon()) diff --git a/test/browser/wretch.spec.js b/test/browser/wretch.spec.js index a7f9674..11477dc 100644 --- a/test/browser/wretch.spec.js +++ b/test/browser/wretch.spec.js @@ -315,21 +315,46 @@ describe("Wretch", function () { expect(await wretch(`${_URL}/accept`).accept("application/json").get().json()).toEqual({ json: "ok" }) }) - it("should set the Authorization header", async function () { - try { - await wretch(_URL + "/basicauth") + describe("handling of the Authorization header", function () { + it("should fail without using an Authorization header", async function () { + try { + await wretch(_URL + "/basicauth") + .get() + .res(_ => fail("Authenticated route should not respond without credentials.")) + } catch (e) { + expect(e.status).toBe(401) + } + }) + + it("should set the Authorization header using the .auth() method", async function () { + const res = await wretch(_URL + "/basicauth") + .auth("Basic d3JldGNoOnLDtmNrcw==") .get() - .res(_ => fail("Authenticated route should not respond without credentials.")) - } catch (e) { - expect(e.status).toBe(401) - } + .text() - const res = await wretch(_URL + "/basicauth") - .auth("Basic d3JldGNoOnJvY2tz") - .get() - .text() + expect(res).toBe("ok") + }) + + it("should set the Authorization header using the BasicAuth addon's .basicAuth() method", async function () { + const res = await wretch(_URL + "/basicauth") + .basicAuth("wretch", "röcks") + .get() + .text() + + expect(res).toBe("ok") + }) - expect(res).toBe("ok") + it("should set the Authorization header using credentials from URL via the BasicAuth addon", async function () { + const url = new URL(_URL) + url.username = "wretch" + url.password = "röcks" + url.pathname = "/basicauth" + const res = await wretch(url.toString()) + .get() + .text() + + expect(res).toBe("ok") + }) }) it("should change the parsing used in the default error handler", async function () { @@ -471,7 +496,7 @@ describe("Wretch", function () { .defer((w, url, { token }) => w.auth(token), true) const result = await w - .options({ token: "Basic d3JldGNoOnJvY2tz" }) + .options({ token: "Basic d3JldGNoOnLDtmNrcw==" }) .options({ q: "a" }) .get("") .text() diff --git a/test/deno/wretch_test.ts b/test/deno/wretch_test.ts index b7715ee..885fa20 100644 --- a/test/deno/wretch_test.ts +++ b/test/deno/wretch_test.ts @@ -9,6 +9,7 @@ import { } from "https://deno.land/std@0.147.0/testing/bdd.ts" import wretchFn from "../../dist/bundle/wretch.min.mjs" +import BasicAuthAddon from "../../dist/bundle/addons/basicAuth.min.mjs" import FormUrlAddon from "../../dist/bundle/addons/formUrl.min.mjs" import FormDataAddon from "../../dist/bundle/addons/formData.min.mjs" import QueryAddon from "../../dist/bundle/addons/queryString.min.mjs" @@ -365,21 +366,48 @@ describe("Wretch", function () { assertEquals(await wretch(`${_URL}/accept`).accept("application/json").get().json(), { json: "ok" }) }) - it("should set the Authorization header", async function () { - try { - await wretch(_URL + "/basicauth") + describe("handling of the Authorization header", function () { + it("should fail without an Authorization header", async function () { + try { + await wretch(_URL + "/basicauth") + .get() + .res(_ => fail("Authenticated route should not respond without credentials.")) + } catch (e) { + assertEquals(e.status, 401) + } + }) + + it("should set the Authorization header using the .auth() method", async function () { + const res = await wretch(_URL + "/basicauth") + .auth("Basic d3JldGNoOnLDtmNrcw==") .get() - .res(_ => fail("Authenticated route should not respond without credentials.")) - } catch (e) { - assertEquals(e.status, 401) - } + .text() + + assertEquals(res, "ok") + }) - const res = await wretch(_URL + "/basicauth") - .auth("Basic d3JldGNoOnJvY2tz") - .get() - .text() + it("should set the Authorization header using the BasicAuth addon's .basicAuth() method", async function () { + const res = await wretch(_URL + "/basicauth") + .addon(BasicAuthAddon) + .basicAuth("wretch", "röcks") + .get() + .text() + + assertEquals(res, "ok") + }) - assertEquals(res, "ok") + it("should set the Authorization header using credentials from URL via the BasicAuth addon", async function () { + const url = new URL(_URL) + url.username = "wretch" + url.password = "röcks" + url.pathname = "/basicauth" + const res = await wretch(url.toString()) + .addon(BasicAuthAddon) + .get() + .text() + + assertEquals(res, "ok") + }) }) it("should change the parsing used in the default error handler", async function () { @@ -537,7 +565,7 @@ describe("Wretch", function () { .defer((w, url, { token }) => w.auth(token), true) const result = await w - .options({ token: "Basic d3JldGNoOnJvY2tz" }) + .options({ token: "Basic d3JldGNoOnLDtmNrcw==" }) .options({ q: "a" }) .get("") .text() diff --git a/test/mock.js b/test/mock.js index a35e8ca..53e8ad3 100644 --- a/test/mock.js +++ b/test/mock.js @@ -19,7 +19,7 @@ const preload = { } async function validate(username, password, req, reply) { - if (username !== 'wretch' || password !== 'rocks') { + if (username !== 'wretch' || password !== 'röcks') { return new Error('Winter is coming') } } diff --git a/test/node/wretch.spec.ts b/test/node/wretch.spec.ts index ff11b8e..018ae43 100644 --- a/test/node/wretch.spec.ts +++ b/test/node/wretch.spec.ts @@ -8,6 +8,7 @@ import wretch from "../../src" import { mix } from "../../src/utils" import AbortAddon from "../../src/addons/abort" +import BasicAuthAddon from "../../src/addons/basicAuth" import FormDataAddon from "../../src/addons/formData" import FormUrlAddon from "../../src/addons/formUrl" import PerfsAddon from "../../src/addons/perfs" @@ -469,22 +470,49 @@ describe("Wretch", function () { expect(await wretch(`${_URL}/accept`).accept("application/json").get().json()).toEqual({ json: "ok" }) }) - it("should set the Authorization header", async function () { - try { - await wretch(_URL + "/basicauth") + describe("handling of the Authorization header", function () { + it("should fail without using an Authorization header", async function () { + try { + await wretch(_URL + "/basicauth") + .get() + .res(_ => fail("Authenticated route should not respond without credentials.")) + } catch (e) { + expect(e.status).toBe(401) + expect(e.url).toBe(_URL + "/basicauth") + } + }) + + it("should set the Authorization header using the .auth() method", async function () { + const res = await wretch(_URL + "/basicauth") + .auth("Basic d3JldGNoOnLDtmNrcw==") .get() - .res(_ => fail("Authenticated route should not respond without credentials.")) - } catch (e) { - expect(e.status).toBe(401) - expect(e.url).toBe(_URL + "/basicauth") - } + .text() - const res = await wretch(_URL + "/basicauth") - .auth("Basic d3JldGNoOnJvY2tz") - .get() - .text() + expect(res).toBe("ok") + }) - expect(res).toBe("ok") + it("should set the Authorization header using the BasicAuth addon's .basicAuth() method", async function () { + const res = await wretch(_URL + "/basicauth") + .addon(BasicAuthAddon) + .basicAuth("wretch", "röcks") + .get() + .text() + + expect(res).toBe("ok") + }) + + it("should set the Authorization header using credentials from URL via the BasicAuth addon", async function () { + const url = new URL(_URL) + url.username = "wretch" + url.password = "röcks" + url.pathname = "/basicauth" + const res = await wretch(url.toString()) + .addon(BasicAuthAddon) + .get() + .text() + + expect(res).toBe("ok") + }) }) it("should change the parsing used in the default error handler", async function () { @@ -671,7 +699,7 @@ describe("Wretch", function () { .defer((w, url, { token }) => w.auth(token), true) const result = await w - .options({ token: "Basic d3JldGNoOnJvY2tz" }) + .options({ token: "Basic d3JldGNoOnLDtmNrcw==" }) .options({ q: "a" }) .get("") .text() @@ -683,7 +711,7 @@ describe("Wretch", function () { .get() .unauthorized((_, request) => { return request - .auth("Basic d3JldGNoOnJvY2tz") + .auth("Basic d3JldGNoOnLDtmNrcw==") .fetch() .text() }) diff --git a/test/tsconfig.json b/test/tsconfig.json index 8bdbe39..959e4a6 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2015", "lib": [ "es2015", "dom", @@ -18,4 +18,4 @@ "include": [ "./node/**/*.spec.ts" ] -} \ No newline at end of file +} From d36c6b4dac91d0b42a8a4390a1f75a0de00025ca Mon Sep 17 00:00:00 2001 From: "Dimitri B." Date: Sun, 19 May 2024 18:19:35 +0200 Subject: [PATCH 2/4] Don't use polyfill system for TextEncoder --- src/addons/basicAuth.ts | 11 +++-------- src/config.ts | 1 - 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/addons/basicAuth.ts b/src/addons/basicAuth.ts index 4609616..a53ea8c 100644 --- a/src/addons/basicAuth.ts +++ b/src/addons/basicAuth.ts @@ -1,11 +1,7 @@ import type { Config, ConfiguredMiddleware, Wretch, WretchAddon } from "../types.js" -function utf8ToBase64( - input: string, - config: Config, - textEncoder = config.polyfill("TextEncoder", true, true) as TextEncoder, -) { - const utf8Bytes = textEncoder.encode(input) +function utf8ToBase64(input: string) { + const utf8Bytes = new TextEncoder().encode(input) return btoa(String.fromCharCode(...utf8Bytes)) } @@ -41,7 +37,6 @@ const makeBasicAuthMiddleware: (config: Config) => ConfiguredMiddleware = config if (parsedUrl?.username || parsedUrl?.password) { const basicAuthBase64 = utf8ToBase64( `${decodeURIComponent(parsedUrl.username)}:${decodeURIComponent(parsedUrl.password)}`, - config ) opts.headers = { ...opts.headers, @@ -71,7 +66,7 @@ const basicAuth: WretchAddon = { }, wretch: { basicAuth(username, password) { - const basicAuthBase64 = utf8ToBase64(`${username}:${password}`, this._config) + const basicAuthBase64 = utf8ToBase64(`${username}:${password}`) return this.auth(`Basic ${basicAuthBase64}`) }, }, diff --git a/src/config.ts b/src/config.ts index dcf618b..6daebbd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,7 +17,6 @@ const config: Config = { // performance: null, // PerformanceObserver: null, // AbortController: null, - // TextEncoder: null }, polyfill(p: string, doThrow: boolean = true, instance: boolean = false, ...args: any[]) { const res = this.polyfills[p] || From 1c38c6f059043a5dd9f8323445ab046f8b932706 Mon Sep 17 00:00:00 2001 From: "Dimitri B." Date: Sun, 19 May 2024 18:31:42 +0200 Subject: [PATCH 3/4] Improve types --- src/addons/basicAuth.ts | 2 +- src/addons/formData.ts | 2 +- src/addons/perfs.ts | 28 +++++++++++++++++++++------- src/types.ts | 13 +++++++++++++ test/deno/wretch_test.ts | 2 +- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/addons/basicAuth.ts b/src/addons/basicAuth.ts index a53ea8c..507144d 100644 --- a/src/addons/basicAuth.ts +++ b/src/addons/basicAuth.ts @@ -31,7 +31,7 @@ export interface BasicAuthAddon { } const makeBasicAuthMiddleware: (config: Config) => ConfiguredMiddleware = config => next => (url, opts) => { - const _URL = config.polyfill("URL") as typeof URL + const _URL = config.polyfill("URL") const parsedUrl = _URL.canParse(url) ? new _URL(url) : null if (parsedUrl?.username || parsedUrl?.password) { diff --git a/src/addons/formData.ts b/src/addons/formData.ts index 9824aaf..2c1faa0 100644 --- a/src/addons/formData.ts +++ b/src/addons/formData.ts @@ -5,7 +5,7 @@ function convertFormData( recursive: string[] | boolean = false, config: Config, formData = config.polyfill("FormData", true, true), - ancestors = [], + ancestors = [] as string[], ) { Object.entries(formObject).forEach(([key, value]) => { let formKey = ancestors.reduce((acc, ancestor) => ( diff --git a/src/addons/perfs.ts b/src/addons/perfs.ts index 3d21d28..b9a7e78 100644 --- a/src/addons/perfs.ts +++ b/src/addons/perfs.ts @@ -1,4 +1,6 @@ -import type { WretchResponseChain, WretchAddon } from "../types.js" +import type { WretchResponseChain, WretchAddon, Config } from "../types.js" + +export type PerfCallback = (timing: any) => void export interface PerfsAddon { /** @@ -6,7 +8,7 @@ export interface PerfsAddon { * * Warning: Still experimental on browsers and node.js */ - perfs: (this: C & WretchResponseChain, cb?: (timing: any) => void) => this, + perfs: (this: C & WretchResponseChain, cb?: PerfCallback) => this, } /** @@ -56,10 +58,15 @@ export interface PerfsAddon { * ``` */ const perfs: () => WretchAddon = () => { - const callbacks = new Map() - let observer = null + const callbacks = new Map() + let observer /*: PerformanceObserver | null*/ = null - const onMatch = (entries, name, callback, performance) => { + const onMatch = ( + entries /*: PerformanceObserverEntryList */, + name: string, + callback: PerfCallback, + performance: typeof globalThis.performance + ) => { if (!entries.getEntriesByName) return false const matches = entries.getEntriesByName(name) @@ -80,7 +87,10 @@ const perfs: () => WretchAddon = () => { return false } - const initObserver = (performance, performanceObserver) => { + const initObserver = ( + performance: (typeof globalThis.performance) | null | undefined, + performanceObserver /*: (typeof PerformanceObserver) | null | undefined */ + ) => { if (!observer && performance && performanceObserver) { observer = new performanceObserver(entries => { callbacks.forEach((callback, name) => { @@ -95,7 +105,11 @@ const perfs: () => WretchAddon = () => { return observer } - const monitor = (name, callback, config) => { + const monitor = ( + name: string | null | undefined, + callback: PerfCallback | null | undefined, + config: Config + ) => { if (!name || !callback) return diff --git a/src/types.ts b/src/types.ts index abf933c..cb8c581 100644 --- a/src/types.ts +++ b/src/types.ts @@ -740,6 +740,19 @@ export type Config = { options: {}; errorType: string; polyfills: {}; + polyfill(p: "fetch", doThrow?: boolean): typeof fetch; + polyfill(p: "FormData", doThrow: boolean, instance: true, ...args: ConstructorParameters): FormData; + polyfill(p: "FormData", doThrow?: boolean, instance?: false): typeof FormData; + polyfill(p: "URL", doThrow: boolean, instance: true, ...args: ConstructorParameters): URL; + polyfill(p: "URL", doThrow?: boolean, instance?: false): typeof URL; + polyfill(p: "URLSearchParams", doThrow: boolean, instance: true, ...args: ConstructorParameters): URLSearchParams; + polyfill(p: "URLSearchParams", doThrow?: boolean, instance?: false): typeof URLSearchParams; + polyfill(p: "AbortController", doThrow: boolean, instance: true, ...args: ConstructorParameters): AbortController; + polyfill(p: "AbortController", doThrow?: boolean, instance?: false): typeof AbortController; + polyfill(p: "performance", doThrow: boolean): typeof performance; + // not implemented in deno yet, thus type isn't available + // polyfill(p: "PerformanceObserver", doThrow: boolean, instance: true, ...args: ConstructorParameters): PerformanceObserver; + // polyfill(p: "PerformanceObserver", doThrow?: boolean, instance?: false): typeof PerformanceObserver; polyfill(p: string, doThrow?: boolean, instance?: boolean, ...args: any[]): any; } diff --git a/test/deno/wretch_test.ts b/test/deno/wretch_test.ts index 885fa20..3d95654 100644 --- a/test/deno/wretch_test.ts +++ b/test/deno/wretch_test.ts @@ -24,7 +24,7 @@ declare type factory = { polyfills: (polyfills: object, replace?: boolean | undefined) => void; } -const wretch = wretchFn as factory +const wretch = wretchFn as unknown as factory // Deno.test("url test", () => { // const url = new URL("./foo.js", "https://deno.land/") From 57f46f2e2ecd92a4c18b2b9f74923650f25c06a7 Mon Sep 17 00:00:00 2001 From: Julien Elbaz Date: Sat, 1 Jun 2024 12:45:58 +0200 Subject: [PATCH 4/4] Fix node@16 tests --- package-lock.json | 126 +++++++++++++++++++++++++++++---------- package.json | 5 +- test/node/wretch.spec.ts | 11 +++- 3 files changed, 106 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ccfe62..da58b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,8 @@ "tslint": "^6.1.2", "typedoc": "^0.25.3", "typescript": "^5.2.2", - "wait-on": "^7.2.0" + "wait-on": "^7.2.0", + "whatwg-url": "^14.0.0" }, "engines": { "node": ">=14" @@ -6146,6 +6147,28 @@ } } }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/node-fetch/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==", + "dev": true + }, + "node_modules/node-fetch/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==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6595,9 +6618,9 @@ "dev": true }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -7764,10 +7787,16 @@ } }, "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } }, "node_modules/tree-kill": { "version": "1.2.2", @@ -8298,19 +8327,25 @@ } }, "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==", - "dev": true + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } }, "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==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", "dev": true, "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/which": { @@ -13170,6 +13205,30 @@ "dev": true, "requires": { "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "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==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } } }, "node-int64": { @@ -13513,9 +13572,9 @@ "dev": true }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "pure-rand": { @@ -14409,10 +14468,13 @@ "dev": true }, "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } }, "tree-kill": { "version": "1.2.2", @@ -14777,19 +14839,19 @@ } }, "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==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true }, "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", "dev": true, "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" } }, "which": { diff --git a/package.json b/package.json index 368aef5..88b6ee5 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,8 @@ "tslint": "^6.1.2", "typedoc": "^0.25.3", "typescript": "^5.2.2", - "wait-on": "^7.2.0" + "wait-on": "^7.2.0", + "whatwg-url": "^14.0.0" }, "jest": { "testPathIgnorePatterns": [ @@ -167,4 +168,4 @@ "src/addons/index.ts" ] } -} \ No newline at end of file +} diff --git a/test/node/wretch.spec.ts b/test/node/wretch.spec.ts index 018ae43..ebe6174 100644 --- a/test/node/wretch.spec.ts +++ b/test/node/wretch.spec.ts @@ -6,6 +6,7 @@ import { URLSearchParams } from "url" import * as path from "path" import wretch from "../../src" import { mix } from "../../src/utils" +import { URL as URLPolyfill } from 'whatwg-url' import AbortAddon from "../../src/addons/abort" import BasicAuthAddon from "../../src/addons/basicAuth" @@ -493,11 +494,14 @@ describe("Wretch", function () { it("should set the Authorization header using the BasicAuth addon's .basicAuth() method", async function () { const res = await wretch(_URL + "/basicauth") + .polyfills({ + URL: URLPolyfill + }) .addon(BasicAuthAddon) .basicAuth("wretch", "röcks") .get() .text() - + expect(res).toBe("ok") }) @@ -507,10 +511,13 @@ describe("Wretch", function () { url.password = "röcks" url.pathname = "/basicauth" const res = await wretch(url.toString()) + .polyfills({ + URL: URLPolyfill + }) .addon(BasicAuthAddon) .get() .text() - + expect(res).toBe("ok") }) })