Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@osskit/fetch-enhancers",
"version": "4.6.0",
"version": "4.7.0",
"repository": {
"url": "https://github.com/osskit/fetch-enhancers"
},
Expand Down Expand Up @@ -49,4 +49,4 @@
"*.{ts,js,json}": "prettier --write"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
}
24 changes: 20 additions & 4 deletions src/enhancers/withHeaders.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import type { Fetch } from '../types.js';

const headersToObject = (headers?: HeadersInit): Record<string, string> | undefined => {
if (!headers) {
return undefined;
}
if (headers instanceof Headers) {
return Object.fromEntries(headers.entries());
}
if (Array.isArray(headers)) {
return Object.fromEntries(headers);
}
if (typeof headers === 'object') {
return { ...headers };
}
return undefined;
};

export const withHeaders =
(fetch: Fetch, getHeaders: (headers?: HeadersInit) => HeadersInit): Fetch =>
(url, init) =>
(fetch: Fetch, getHeaders: (headers?: HeadersInit) => HeadersInit | Promise<HeadersInit>): Fetch =>
async (url, init) =>
fetch(url, {
...init,
headers: {
...init?.headers,
...getHeaders(init?.headers),
...headersToObject(init?.headers),
...headersToObject(await getHeaders(init?.headers)),
},
});
21 changes: 6 additions & 15 deletions src/enhancers/withToken.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import type { Fetch } from '../types.js';
import { withHeaders } from './withHeaders.js';

export const withToken =
(getToken: () => Promise<string> | string, fetch: Fetch): Fetch =>
async (url, innerInit) => {
const innerFetch = fetch;
const token = await getToken();

return innerFetch(url, {
...innerInit,
headers: {
...innerInit?.headers,
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Bearer ${token}`,
},
});
};
export const withToken = (getToken: () => Promise<string> | string, fetch: Fetch): Fetch =>
withHeaders(fetch, async () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Bearer ${await getToken()}`,
}));
65 changes: 64 additions & 1 deletion tests/specs/withHeaders.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,70 @@ describe('withHeaders', () => {

const { port } = server.address() as AddressInfo;
const res = await fetchWithExtraHeaders(`http://127.0.0.1:${port}`, { headers: { id: 'id', bar: 'bar' } });
await expect(res.text()).resolves.toMatchInlineSnapshot(`"{"id":"override","bar":"bar","foo":"foo"}"`);
await expect(res.json()).resolves.toStrictEqual({ id: 'override', bar: 'bar', foo: 'foo' });
server.close();
});

it('add headers when getHeaders returns a promise', async () => {
const fetchWithAsyncHeaders = withHeaders(fetch, async () => ({ foo: 'foo', id: 'override' }));
const server = createServer((req, res) => {
const response = JSON.stringify({ id: req.headers.id, bar: req.headers.bar, foo: req.headers.foo });
res.writeHead(200);
res.end(response);
});

await waitForServer(server);
const { port } = server.address() as AddressInfo;
const res = await fetchWithAsyncHeaders(`http://127.0.0.1:${port}`, { headers: { id: 'id', bar: 'bar' } });
await expect(res.json()).resolves.toStrictEqual({ id: 'override', bar: 'bar', foo: 'foo' });
server.close();
});

it('merges headers when init.headers is a Headers instance', async () => {
const server = createServer((req, res) => {
const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar });
res.writeHead(200);
res.end(response);
});

await waitForServer(server);
const { port } = server.address() as AddressInfo;
const headers = new Headers({ bar: 'baz' });
const fetchWithHeadersInstance = withHeaders(fetch, () => ({ foo: 'bar' }));
const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers });
await expect(res.json()).resolves.toStrictEqual({ foo: 'bar', bar: 'baz' });
server.close();
});

it('merges headers when init.headers is an object and getHeaders returns a Headers instance', async () => {
const server = createServer((req, res) => {
const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar });
res.writeHead(200);
res.end(response);
});

await waitForServer(server);
const { port } = server.address() as AddressInfo;
const headers = new Headers({ bar: 'baz' });
const fetchWithHeadersInstance = withHeaders(fetch, () => new Headers({ foo: 'bar' }));
const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers });
await expect(res.json()).resolves.toStrictEqual({ foo: 'bar', bar: 'baz' });
server.close();
});

it('merges headers when init.headers is a Headers instance and getHeaders returns a Headers instance', async () => {
const server = createServer((req, res) => {
const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar });
res.writeHead(200);
res.end(response);
});

await waitForServer(server);
const { port } = server.address() as AddressInfo;
const headers = new Headers({ bar: 'baz' });
const fetchWithHeadersInstance = withHeaders(fetch, () => new Headers({ foo: 'bar' }));
const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers });
await expect(res.json()).resolves.toStrictEqual({ foo: 'bar', bar: 'baz' });
server.close();
});
});
41 changes: 41 additions & 0 deletions tests/specs/withToken.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createServer } from 'node:http';
import type { AddressInfo } from 'node:net';
import { describe, it, expect } from 'vitest';
import { withToken } from '../../src/index.js';
import { waitForServer } from '../services/waitForServer.js';

describe('withToken', () => {
it('adds token to headers', async () => {
const server = createServer((req, res) => {
const response = JSON.stringify({
authorization: req.headers.authorization,
});
res.writeHead(200);
res.end(response);
});

await waitForServer(server);
const { port } = server.address() as AddressInfo;
const fetchWithToken = withToken(() => 'mytoken', fetch);
const res = await fetchWithToken(`http://127.0.0.1:${port}`);
await expect(res.text()).resolves.toBe('{"authorization":"Bearer mytoken"}');
});

it('merges headers when init.headers is a Headers instance', async () => {
const server = createServer((req, res) => {
const response = JSON.stringify({
authorization: req.headers.authorization,
foo: req.headers.foo,
});
res.writeHead(200);
res.end(response);
});
await waitForServer(server);
const { port } = server.address() as AddressInfo;
const headers = new Headers({ foo: 'bar' });
const fetchWithToken = withToken(() => 'mytoken', fetch);
const res = await fetchWithToken(`http://127.0.0.1:${port}`, { headers });
await expect(res.text()).resolves.toBe('{"authorization":"Bearer mytoken","foo":"bar"}');
server.close();
});
});