Skip to content

Commit

Permalink
Merge pull request #9 from krutoo/jwt-middleware
Browse files Browse the repository at this point in the history
JWT middleware
  • Loading branch information
krutoo authored Sep 26, 2024
2 parents f18044a + cbaa4e3 commit e1869c8
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 48 deletions.
11 changes: 0 additions & 11 deletions .prettierrc.json

This file was deleted.

40 changes: 22 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Fetch tools

Set of utilities for JS [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) function.
Set of utilities for JS
[fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) function.

## Goals

Expand All @@ -26,21 +27,19 @@ bun add @krutoo/fetch-tools
Creating fetch with some extra features.

```ts
import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools';
import { validateStatus, defaultHeaders, log } from '@krutoo/fetch-tools/middleware';
import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools';
import { defaultHeaders, log, validateStatus } from '@krutoo/fetch-tools/middleware';

// configure your own fetch...
const myFetch = configureFetch(
fetch,
applyMiddleware(
// validate status (like in axios)
validateStatus(status => status >= 200 && status < 300),

validateStatus((status) => status >= 200 && status < 300),
// add default headers
defaultHeaders({
'user-agent': 'test',
}),

// log request stages (before request, after response, on catch)
log({
onCatch: ({ error }) => console.error(error),
Expand All @@ -50,8 +49,8 @@ const myFetch = configureFetch(

// ...and using it like normal fetch
myFetch('posts/1')
.then(res => res.json())
.then(data => console.log(data));
.then((res) => res.json())
.then((data) => console.log(data));
```

## Middleware
Expand Down Expand Up @@ -83,14 +82,14 @@ async function myMiddleware(request, next) {
Returns a middleware that will validate status.

```ts
import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools';
import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools';
import { validateStatus } from '@krutoo/fetch-tools/middleware';

const myFetch = configureFetch(
fetch,
applyMiddleware(
// fetch promise will be rejected when status is not valid
validateStatus(status => status >= 200 && status < 300),
validateStatus((status) => status >= 200 && status < 300),
),
);
```
Expand All @@ -100,7 +99,7 @@ const myFetch = configureFetch(
Returns a middleware that will set default headers to request.

```ts
import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools';
import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools';
import { defaultHeaders } from '@krutoo/fetch-tools/middleware';

const myFetch = configureFetch(
Expand All @@ -117,7 +116,7 @@ const myFetch = configureFetch(
Returns a middleware that will log phases by handler.

```ts
import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools';
import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools';
import { log } from '@krutoo/fetch-tools/middleware';

const myFetch = configureFetch(
Expand Down Expand Up @@ -160,7 +159,7 @@ const enhance = applyMiddleware(
);

Deno.serve(
enhance(req => {
enhance((req) => {
return new Response('<h1>Main page</h1>');
}),
);
Expand Down Expand Up @@ -212,7 +211,8 @@ Bun.serve({

Currently there is no builtin server implementation based on Fetch API in Node.js.

It is possible to use _adapter_ for `node:http` or `express` from [@whatwg-node/server](https://www.npmjs.com/package/@whatwg-node/server).
It is possible to use _adapter_ for `node:http` or `express` from
[@whatwg-node/server](https://www.npmjs.com/package/@whatwg-node/server).

```ts
import { router } from '@krutoo/fetch-tools';
Expand All @@ -237,7 +237,7 @@ server.listen(8080);
You can use middleware for server handlers too:

```ts
import { router, applyMiddleware } from '@krutoo/fetch-tools';
import { applyMiddleware, router } from '@krutoo/fetch-tools';
import { log } from '@krutoo/fetch-tools/middleware';

const enhance = applyMiddleware(
Expand Down Expand Up @@ -266,13 +266,17 @@ Cookies can be used in different ways on the server.

### Browser like behavior

If you want to imitate browser behavior as much as possible in terms of working with cookies, you can use `@krutoo/fetch-tools` together with `fetch-cookie`.
If you want to imitate browser behavior as much as possible in terms of working with cookies, you
can use `@krutoo/fetch-tools` together with `fetch-cookie`.

To use **fetch-cookie** as an middleware, follow [these](https://github.com/valeriangalliat/fetch-cookie/issues/79#issuecomment-1672188226) instructions.
To use **fetch-cookie** as an middleware, follow
[these](https://github.com/valeriangalliat/fetch-cookie/issues/79#issuecomment-1672188226)
instructions.

### Microfrontends

Server part of the microfrontend can make requests to some HTTP API on behalf of the user, sending his cookies in requests.
Server part of the microfrontend can make requests to some HTTP API on behalf of the user, sending
his cookies in requests.

In this case you can use just `defaultHeaders` middleware:

Expand Down
4 changes: 2 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@krutoo/fetch-tools",
"version": "0.0.0",
"tasks": {
"lint": "deno lint && deno check src/**/*.ts",
"lint": "deno check src/**/*.ts && deno lint && deno fmt --check",
"build-npm": "deno run -A scripts/build-npm.ts"
},
"imports": {
Expand All @@ -21,7 +21,7 @@
"indentWidth": 2,
"semiColons": true,
"singleQuote": true,
"exclude": ["npm/**/*", "example/**/*"]
"exclude": ["npm/**/*"]
},
"lint": {
"exclude": ["npm/**/*"]
Expand Down
10 changes: 5 additions & 5 deletions src/configure.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Handler, Enhancer, Middleware } from './types.ts';
import type { Enhancer, Handler, Middleware } from './types.ts';

/**
* Enhance fetch function by provided enhancer.
Expand All @@ -12,7 +12,7 @@ export function configureFetch<T extends typeof fetch>(
fetchFn: T,
enhance?: Enhancer,
): typeof fetch {
let inner: Handler = request => fetchFn(request);
let inner: Handler = (request) => fetchFn(request);

if (enhance) {
inner = enhance(inner);
Expand All @@ -28,15 +28,15 @@ export function configureFetch<T extends typeof fetch>(
*/
export function applyMiddleware(...list: Array<Middleware>): Enhancer {
if (list.length === 0) {
return handler => handler;
return (handler) => handler;
}

return handler => {
return (handler) => {
let result = handler;

for (const item of list.reverse()) {
const next = result;
result = request => Promise.resolve(item(request, next));
result = (request) => Promise.resolve(item(request, next));
}

return result;
Expand Down
17 changes: 15 additions & 2 deletions src/middleware/default-headers.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import type { Middleware } from '../types.ts';

/** Options of default headers middleware. */
export interface DefaultHeadersOptions {
/**
* How to add header to request headers.
* - "set" - headers will be added using "set" method
* - "append" - headers will be added using "append" method
*/
strategy?: 'set' | 'append';
}

/**
* Returns a middleware that will set default headers to request.
* @param defaults Default headers.
* @return Middleware.
*/
export function defaultHeaders(defaults: HeadersInit): Middleware {
export function defaultHeaders(
defaults: HeadersInit,
{ strategy = 'append' }: DefaultHeadersOptions = {},
): Middleware {
return (request, next) => {
// IMPORTANT: for avoid mutate request, just create new Headers and Request here
const headers = new Headers(defaults);

if (request.headers) {
new Headers(request.headers).forEach((value, key) => {
headers.append(key, value);
headers[strategy](key, value);
});
}

Expand Down
35 changes: 35 additions & 0 deletions src/middleware/jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Middleware } from '../types.ts';

/** Options of JWT middleware. */
export interface JwtMiddlewareOptions {
/** JWT Token. */
token: string | (() => string | Promise<string>);

/** Filter. Takes request, should return boolean. When returns false, JWT payload will not be added to request. */
filter?: (request: Request) => boolean;
}

/**
* Simple JWT middleware. Will add "Authorization" header with JWT token to request.
* @param options Options.
* @returns Middleware.
*/
export function jwt({
token,
filter = () => true,
}: JwtMiddlewareOptions): Middleware {
const getToken = typeof token === 'function' ? token : () => token;

return async (request, next) => {
if (!filter(request)) {
return next(request);
}

// IMPORTANT: for avoid mutate request, just create new Headers and Request here
const headers = new Headers(request.headers);

headers.set('Authorization', `Bearer ${await getToken()}`);

return next(new Request(request, { headers }));
};
}
12 changes: 9 additions & 3 deletions src/middleware/mod.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
export { defaultHeaders } from './default-headers.ts';
export { defaultHeaders, type DefaultHeadersOptions } from './default-headers.ts';

export {
log,
type LogData,
type DoneLogData,
type FailLogData,
log,
type LogData,
type LogHandler,
type LogHandlerFactory,
} from './log.ts';

export { retry } from './retry.ts';

export { validateStatus, type ValidateStatusOptions } from './validate-status.ts';

export { proxy, type ProxyOptions, type ProxyRequestFilter } from './proxy.ts';

export { jwt, type JwtMiddlewareOptions } from './jwt.ts';
6 changes: 3 additions & 3 deletions src/middleware/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface ProxyOptions {
* Simple proxy middleware for servers based on Web Fetch API.
* Based on good article: https://blog.r0b.io/post/creating-a-proxy-with-deno/
*/
export function proxy({ filter, target, pathRewrite = p => p }: ProxyOptions): Middleware {
export function proxy({ filter, target, pathRewrite = (p) => p }: ProxyOptions): Middleware {
const matches = createMatches(filter);

const createRequest = (url: URL, request: Request) => {
Expand Down Expand Up @@ -51,10 +51,10 @@ export function proxy({ filter, target, pathRewrite = p => p }: ProxyOptions): M
function createMatches(filter: ProxyOptions['filter']): ProxyRequestFilter {
switch (true) {
case Array.isArray(filter):
return url => filter.some(item => url.pathname.startsWith(item));
return (url) => filter.some((item) => url.pathname.startsWith(item));

case typeof filter === 'string':
return url => url.pathname.startsWith(filter);
return (url) => url.pathname.startsWith(filter);

case typeof filter === 'function':
return filter;
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/validate-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface ValidateStatusOptions {
export function validateStatus(
validate: (status: number, request: Request, response: Response) => boolean,
{
getThrowable = response => new Error(`Request failed with status ${response.status}`),
getThrowable = (response) => new Error(`Request failed with status ${response.status}`),
needDump = true,
}: ValidateStatusOptions = {},
): Middleware {
Expand Down
6 changes: 3 additions & 3 deletions src/mod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type { Handler, Enhancer, Middleware } from './types.ts';
export { configureFetch, applyMiddleware } from './configure.ts';
export type { Enhancer, Handler, Middleware } from './types.ts';
export { applyMiddleware, configureFetch } from './configure.ts';
export { html, json } from './response.ts';
export { router, route } from './server.ts';
export { route, router } from './server.ts';
export { dump } from './utils/dump.ts';

0 comments on commit e1869c8

Please sign in to comment.