Skip to content

Instantly share code, notes, and snippets.

@otofune
Last active March 24, 2020 03:05
Show Gist options
  • Save otofune/664db8825efe891bc6e7a44977eb43a1 to your computer and use it in GitHub Desktop.
Save otofune/664db8825efe891bc6e7a44977eb43a1 to your computer and use it in GitHub Desktop.
import fetch, { Response, RequestInfo, RequestInit } from "node-fetch";
import toughCookie from "tough-cookie";
type ArgumentsOf<T extends Function> = T extends (...args: infer T) => any
? T
: void;
export type Fetch = (
info: RequestInfo,
init?: Omit<RequestInit, "headers"> & {
headers?: { [k: string]: string };
} & { credentials?: "omit" | "includes" }
) => Promise<Response>;
export const requestHeaderMiddleware = (headers: {
[k: string]: string;
}): RequestMiddleware => {
return (info, init = {}) => [
info,
{
...init,
headers: {
...(init.headers ? init.headers : {}),
...headers
}
}
];
};
// (middlewares: ((...args: T) => T)[]) => T[]
const sameChain = <T extends Array<unknown>>(
...chain: ((...args: T) => T)[]
) => {
return (...fi: T): T => chain.reduce((i, h) => h(...i), fi);
};
type FetchMiddleware = (target: Fetch) => Fetch;
export const applyFetchMiddlewares = (middlewares: FetchMiddleware[]) => (
target: Fetch
): Fetch => {
const m = middlewares.map(m => (f: Fetch): [Fetch] => [m(f)]);
return sameChain<[Fetch]>(...m)(target)[0];
};
type RequestMiddleware = (...args: ArgumentsOf<Fetch>) => ArgumentsOf<Fetch>;
export const applyRequestMiddlewares = (
middlewares: RequestMiddleware[]
): FetchMiddleware => target => (info, init) => {
const args = sameChain<ArgumentsOf<Fetch>>(...middlewares)(info, init);
return target(...args);
};
type ResponseMiddleware = (
response: Response,
requestOptions: ArgumentsOf<Fetch>
) => void | Promise<void>;
export const applyResponseMiddlewares = (
middlewares: ResponseMiddleware[]
): FetchMiddleware => target => (...req) => {
return target(...req).then(async r => {
for (const m of middlewares) {
await m(r, req);
}
return r;
});
};
export const bakedFetch = (jar: toughCookie.CookieJar): Fetch =>
applyFetchMiddlewares([
applyRequestMiddlewares([
(info, init) => {
if (init && init.credentials !== "includes") return [info, init];
const Cookie = jar.getCookieStringSync(info.toString());
if (!Cookie.length) return [info, init];
return requestHeaderMiddleware({
Cookie
})(info, init);
},
(info, init = {}) => {
console.log(`-> ${info.toString()}`);
const headers = Array.from(Object.entries(init.headers || {})).reduce(
(s, [k, v]) => s + (s.length ? `, ` : "") + `${k}=${v}`,
""
);
console.log(` headers: [${headers}]`);
return [info, init];
}
]),
applyResponseMiddlewares([
r => {
console.log(`<- ${r.status} ${r.statusText}`);
},
async (r, [, init]) => {
if (r.ok) return;
if (init && init.body) {
console.error(init.body);
}
console.error(await r.text());
},
(r, [, init]) => {
if (!init || init.credentials != "includes") return;
if (!r.headers.has("set-cookie")) return;
r.headers.raw()["set-cookie"].forEach(v => {
jar.setCookieSync(v, r.url);
});
}
])
])(fetch);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment