Skip to content

Commit

Permalink
Add client option to pass custom RequestInit object into fetch reques…
Browse files Browse the repository at this point in the history
…ts for supported implementations (#2020)

* Add client option to pass custom RequestInit object into fetch requests for supported implementations

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - formatting

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - formatting

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - fix e2e test failures

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - refactor test to use own server/endpoint

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - refactor test to use own server/endpoint

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - refactor test to use own server/endpoint

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - refactor test to use own server/endpoint

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - fix lockfile

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - add changeset

* Add client option to pass custom RequestInit object into fetch requests for supported implementations - add changeset
  • Loading branch information
mellster2012 authored Dec 2, 2024
1 parent 75399f8 commit 7081842
Show file tree
Hide file tree
Showing 6 changed files with 498 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/angry-emus-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-fetch": patch
---

Add client option to pass custom RequestInit object into fetch requests for supported implementations
3 changes: 3 additions & 0 deletions packages/openapi-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,15 @@
"del-cli": "^5.1.0",
"esbuild": "^0.24.0",
"execa": "^8.0.1",
"express": "^4.21.1",
"feature-fetch": "^0.0.15",
"node-forge": "^1.3.1",
"openapi-typescript": "workspace:^",
"openapi-typescript-codegen": "^0.25.0",
"openapi-typescript-fetch": "^2.0.0",
"superagent": "^10.1.1",
"typescript": "^5.7.2",
"undici": "^6.21.0",
"vite": "^6.0.1"
}
}
2 changes: 2 additions & 0 deletions packages/openapi-fetch/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export interface ClientOptions extends Omit<RequestInit, "headers"> {
/** global bodySerializer */
bodySerializer?: BodySerializer<unknown>;
headers?: HeadersOptions;
/** RequestInit extension object to pass as 2nd argument to fetch when supported (defaults to undefined) */
requestInitExt?: Record<string, unknown>;
}

export type HeadersOptions =
Expand Down
12 changes: 11 additions & 1 deletion packages/openapi-fetch/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
// settings & const
const PATH_PARAM_RE = /\{[^{}]+\}/g;

const supportsRequestInitExt = () => {
return (
typeof process === "object" &&
Number.parseInt(process?.versions?.node?.substring(0, 2)) >= 18 &&
process.versions.undici
);
};

/**
* Returns a cheap, non-cryptographically-secure random ID
* Courtesy of @imranbarbhuiya (https://github.com/imranbarbhuiya)
Expand All @@ -21,8 +29,10 @@ export default function createClient(clientOptions) {
querySerializer: globalQuerySerializer,
bodySerializer: globalBodySerializer,
headers: baseHeaders,
requestInitExt = undefined,
...baseOptions
} = { ...clientOptions };
requestInitExt = supportsRequestInitExt() ? requestInitExt : undefined;
baseUrl = removeTrailingSlash(baseUrl);
const middlewares = [];

Expand Down Expand Up @@ -126,7 +136,7 @@ export default function createClient(clientOptions) {
// fetch!
let response;
try {
response = await fetch(request);
response = await fetch(request, requestInitExt);
} catch (error) {
let errorAfterMiddleware = error;
// middleware (error)
Expand Down
92 changes: 92 additions & 0 deletions packages/openapi-fetch/test/common/create-client-e2e.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import express from "express";
import { expect, test } from "vitest";
import * as https from "node:https";
import { Agent } from "undici";
import createClient from "../../src/index.js";
import * as forge from "node-forge";
import * as crypto from "node:crypto";

const pki = forge.pki;

const genCACert = async (opts = {}) => {
const options = {
...{
commonName: "Testing CA - DO NOT TRUST",
bits: 2048,
},
...opts,
};

const keyPair = await new Promise((res, rej) => {
pki.rsa.generateKeyPair({ bits: options.bits }, (error, pair) => {
if (error) {
rej(error);
} else {
res(pair);
}
});
});

const cert = pki.createCertificate();
cert.publicKey = keyPair.publicKey;
cert.serialNumber = crypto.randomUUID().replace(/-/g, "");

cert.validity.notBefore = new Date();
cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);

cert.setSubject([{ name: "commonName", value: options.commonName }]);
cert.setExtensions([{ name: "basicConstraints", cA: true }]);

cert.setIssuer(cert.subject.attributes);
cert.sign(keyPair.privateKey, forge.md.sha256.create());

return {
ca: {
key: pki.privateKeyToPem(keyPair.privateKey),
cert: pki.certificateToPem(cert),
},
fingerprint: forge.util.encode64(
pki.getPublicKeyFingerprint(keyPair.publicKey, {
type: "SubjectPublicKeyInfo",
md: forge.md.sha256.create(),
encoding: "binary",
}),
),
};
};

const caToBuffer = (ca) => {
return {
key: Buffer.from(ca.key),
cert: Buffer.from(ca.cert),
};
};

const API_PORT = process.env.API_PORT || 4578;

const app = express();
app.get("/v1/foo", (req, res) => {
res.send("bar");
});

test("requestInitExt", async () => {
const cert = await genCACert();
const buffers = caToBuffer(cert.ca);
const options = {};
options.key = buffers.key;
options.cert = buffers.cert;
const httpsServer = https.createServer(options, app);
httpsServer.listen(4578);
const dispatcher = new Agent({
connect: {
rejectUnauthorized: false,
},
});
const client = createClient({ baseUrl: `https://localhost:${API_PORT}`, requestInitExt: { dispatcher } });
const fetchResponse = await client.GET("/v1/foo", { parseAs: "text" });
httpsServer.closeAllConnections();
httpsServer.close();
expect(fetchResponse.response.ok).toBe(true);
});
Loading

0 comments on commit 7081842

Please sign in to comment.