Merge branch 'dev' into pr-14-v2

This commit is contained in:
Jorrin
2023-12-26 21:48:32 +01:00
78 changed files with 2364 additions and 700 deletions

View File

@@ -26,15 +26,19 @@ export function makeFullUrl(url: string, ops?: FullUrlOptions): string {
return parsedUrl.toString();
}
export function makeFullFetcher(fetcher: Fetcher): UseableFetcher {
return (url, ops) => {
export function makeFetcher(fetcher: Fetcher): UseableFetcher {
const newFetcher = (url: string, ops?: FetcherOptions) => {
return fetcher(url, {
headers: ops?.headers ?? {},
method: ops?.method ?? 'GET',
query: ops?.query ?? {},
baseUrl: ops?.baseUrl ?? '',
readHeaders: ops?.readHeaders ?? [],
body: ops?.body,
returnRaw: ops?.returnRaw ?? false,
});
};
const output: UseableFetcher = async (url, ops) => (await newFetcher(url, ops)).body;
output.full = newFetcher;
return output;
}

View File

@@ -11,13 +11,17 @@ export type FetchOps = {
export type FetchHeaders = {
get(key: string): string | null;
set(key: string, value: string): void;
};
export type FetchReply = {
text(): Promise<string>;
json(): Promise<any>;
extraHeaders?: FetchHeaders;
extraUrl?: string;
headers: FetchHeaders;
url: string;
status: number;
};
export type FetchLike = (url: string, ops?: FetchOps | undefined) => Promise<FetchReply>;

View File

@@ -9,9 +9,28 @@ const headerMap: Record<string, string> = {
origin: 'X-Origin',
};
const responseHeaderMap: Record<string, string> = {
'x-set-cookie': 'Set-Cookie',
};
export function makeSimpleProxyFetcher(proxyUrl: string, f: FetchLike): Fetcher {
const fetcher = makeStandardFetcher(f);
const proxiedFetch: Fetcher = async (url, ops) => {
const fetcher = makeStandardFetcher(async (a, b) => {
const res = await f(a, b);
// set extra headers that cant normally be accessed
res.extraHeaders = new Headers();
Object.entries(responseHeaderMap).forEach((entry) => {
const value = res.headers.get(entry[0]);
if (!value) return;
res.extraHeaders?.set(entry[0].toLowerCase(), value);
});
// set correct final url
res.extraUrl = res.headers.get('X-Final-Destination') ?? res.url;
return res;
});
const fullUrl = makeFullUrl(url, ops);
const headerEntries = Object.entries(ops.headers).map((entry) => {

View File

@@ -1,8 +1,20 @@
import { serializeBody } from '@/fetchers/body';
import { makeFullUrl } from '@/fetchers/common';
import { FetchLike } from '@/fetchers/fetch';
import { FetchLike, FetchReply } from '@/fetchers/fetch';
import { Fetcher } from '@/fetchers/types';
function getHeaders(list: string[], res: FetchReply): Headers {
const output = new Headers();
list.forEach((header) => {
const realHeader = header.toLowerCase();
const value = res.headers.get(realHeader);
const extraValue = res.extraHeaders?.get(realHeader);
if (!value) return;
output.set(realHeader, extraValue ?? value);
});
return output;
}
export function makeStandardFetcher(f: FetchLike): Fetcher {
const normalFetch: Fetcher = async (url, ops) => {
const fullUrl = makeFullUrl(url, ops);
@@ -21,9 +33,17 @@ export function makeStandardFetcher(f: FetchLike): Fetcher {
return res;
}
let body: any;
const isJson = res.headers.get('content-type')?.includes('application/json');
if (isJson) return res.json();
return res.text();
if (isJson) body = await res.json();
else body = await res.text();
return {
body,
finalUrl: res.extraUrl ?? res.url,
headers: getHeaders(ops.readHeaders, res),
statusCode: res.status,
};
};
return normalFetch;

View File

@@ -5,10 +5,13 @@ export type FetcherOptions = {
headers?: Record<string, string>;
query?: Record<string, string>;
method?: 'HEAD' | 'GET' | 'POST';
readHeaders?: string[];
body?: Record<string, any> | string | FormData | URLSearchParams;
returnRaw?: boolean;
};
// Version of the options that always has the defaults set
// This is to make making fetchers yourself easier
export type DefaultedFetcherOptions = {
baseUrl?: string;
body?: Record<string, any> | string | FormData;
@@ -16,13 +19,23 @@ export type DefaultedFetcherOptions = {
query: Record<string, string>;
method: 'HEAD' | 'GET' | 'POST';
returnRaw: boolean;
readHeaders: string[];
};
export type FetcherResponse<T = any> = {
statusCode: number;
headers: Headers;
finalUrl: string;
body: T;
};
// This is the version that will be inputted by library users
export type Fetcher<T = any> = {
(url: string, ops: DefaultedFetcherOptions): Promise<T>;
(url: string, ops: DefaultedFetcherOptions): Promise<FetcherResponse<T>>;
};
// this feature has some quality of life features
// This is the version that scrapers will be interacting with
export type UseableFetcher<T = any> = {
(url: string, ops?: FetcherOptions): Promise<T>;
full: (url: string, ops?: FetcherOptions) => Promise<FetcherResponse<T>>;
};