Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(graphiql-toolkit)!: accept HeadersInit #3854

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
63 changes: 49 additions & 14 deletions packages/graphiql-toolkit/src/create-fetcher/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ const errorHasCode = (err: unknown): err is { code: string } => {
return typeof err === 'object' && err !== null && 'code' in err;
};

/**
* Merge two Headers instances into one.
*
* Returns a new Headers instance (does not mutate).
*
* Headers are merged by having a copy of the first headers argument apply its `set`
* method to assign each header from the second headers argument. This means that headers
* from the second Headers instance overwrite same-named headers in the first.
*/
const mergeHeadersWithSetStrategy = (headersA: Headers, headersB: Headers) => {
const newHeaders = new Headers(headersA);
for (const [key, value] of headersB.entries()) {
newHeaders.set(key, value);
}
return newHeaders;
};

/**
* Returns true if the name matches a subscription in the AST
*
Expand Down Expand Up @@ -57,14 +74,18 @@ export const isSubscriptionWithName = (
export const createSimpleFetcher =
(options: CreateFetcherOptions, httpFetch: typeof fetch): Fetcher =>
async (graphQLParams: FetcherParams, fetcherOpts?: FetcherOpts) => {
const headers = [
new Headers({
'content-type': 'application/json',
}),
new Headers(options.headers ?? {}),
new Headers(fetcherOpts?.headers ?? {}),
].reduce(mergeHeadersWithSetStrategy, new Headers());
jasonkuhrt marked this conversation as resolved.
Show resolved Hide resolved

const data = await httpFetch(options.url, {
method: 'POST',
body: JSON.stringify(graphQLParams),
headers: {
'content-type': 'application/json',
...options.headers,
...fetcherOpts?.headers,
},
headers,
});
return data.json();
};
Expand Down Expand Up @@ -141,17 +162,19 @@ export const createMultipartFetcher = (
httpFetch: typeof fetch,
): Fetcher =>
async function* (graphQLParams: FetcherParams, fetcherOpts?: FetcherOpts) {
const headers = [
new Headers({
'content-type': 'application/json',
accept: 'application/json, multipart/mixed',
}),
new Headers(options.headers ?? {}),
new Headers(fetcherOpts?.headers ?? {}),
].reduce(mergeHeadersWithSetStrategy, new Headers());

const response = await httpFetch(options.url, {
method: 'POST',
body: JSON.stringify(graphQLParams),
headers: {
'content-type': 'application/json',
accept: 'application/json, multipart/mixed',
...options.headers,
// allow user-defined headers to override
// the static provided headers
...fetcherOpts?.headers,
},
headers,
}).then(r =>
meros<Extract<ExecutionResultPayload, { hasNext: boolean }>>(r, {
multiple: true,
Expand Down Expand Up @@ -187,9 +210,21 @@ export async function getWsFetcher(
return createWebsocketsFetcherFromClient(options.wsClient);
}
if (options.subscriptionUrl) {
const fetcherOptsHeaders = new Headers(fetcherOpts?.headers ?? {});
// @ts-expect-error: Current TS Config target does not support `Headers.entries()` method.
// However it is reported as "widely available" and so should be fine to use. This could
// would be more complicated without it.
// @see https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
const fetcherOptsHeadersEntries: [string, string][] = [
...fetcherOptsHeaders.entries(),
];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this isn't needed. However I don't want to open a pandora's box by changing the tsconfig.json file.

// todo: If there are headers with multiple values, they will be lost. Is this a problem?
jasonkuhrt marked this conversation as resolved.
Show resolved Hide resolved
const fetcherOptsHeadersRecord = Object.fromEntries(
fetcherOptsHeadersEntries,
);
return createWebsocketsFetcherFromUrl(options.subscriptionUrl, {
...options.wsConnectionParams,
...fetcherOpts?.headers,
...fetcherOptsHeadersRecord,
});
}
const legacyWebsocketsClient = options.legacyClient || options.legacyWsClient;
Expand Down
4 changes: 2 additions & 2 deletions packages/graphiql-toolkit/src/create-fetcher/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type FetcherParams = {
};

export type FetcherOpts = {
headers?: { [key: string]: any };
headers?: HeadersInit;
documentAST?: DocumentNode;
};

Expand Down Expand Up @@ -104,7 +104,7 @@ export interface CreateFetcherOptions {
* If you enable the headers editor and the user provides
* A header you set statically here, it will be overridden by their value.
*/
headers?: Record<string, string>;
headers?: HeadersInit;
/**
* Websockets connection params used when you provide subscriptionUrl. graphql-ws `ClientOptions.connectionParams`
*/
Expand Down