diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2024-08-30 17:21:18 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2024-08-30 17:21:18 +0800 |
| commit | efbc2ffe6a1ee5ec38a4396305561d6515d0943e (patch) | |
| tree | 2b4c94f18f2c792dfda813050755d5c0273fae22 /node_modules/swup/src/modules/fetchPage.ts | |
| parent | 43acceb75957c880a5ba5a31aae221394a36d5f6 (diff) | |
node fixes, updates
Diffstat (limited to 'node_modules/swup/src/modules/fetchPage.ts')
| -rw-r--r-- | node_modules/swup/src/modules/fetchPage.ts | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/node_modules/swup/src/modules/fetchPage.ts b/node_modules/swup/src/modules/fetchPage.ts new file mode 100644 index 00000000..96275448 --- /dev/null +++ b/node_modules/swup/src/modules/fetchPage.ts @@ -0,0 +1,114 @@ +import type Swup from '../Swup.js'; +import { Location } from '../helpers.js'; +import type { Visit } from './Visit.js'; + +/** A page object as used by swup and its cache. */ +export interface PageData { + /** The URL of the page */ + url: string; + /** The complete HTML response received from the server */ + html: string; +} + +/** Define how a page is fetched. */ +export interface FetchOptions extends Omit<RequestInit, 'cache'> { + /** The request method. */ + method?: 'GET' | 'POST'; + /** The body of the request: raw string, form data object or URL params. */ + body?: string | FormData | URLSearchParams; + /** The request timeout in milliseconds. */ + timeout?: number; + /** Optional visit object with additional context. @internal */ + visit?: Visit; +} + +export class FetchError extends Error { + url: string; + status?: number; + aborted: boolean; + timedOut: boolean; + constructor( + message: string, + details: { url: string; status?: number; aborted?: boolean; timedOut?: boolean } + ) { + super(message); + this.name = 'FetchError'; + this.url = details.url; + this.status = details.status; + this.aborted = details.aborted || false; + this.timedOut = details.timedOut || false; + } +} + +/** + * Fetch a page from the server, return it and cache it. + */ +export async function fetchPage( + this: Swup, + url: URL | string, + options: FetchOptions = {} +): Promise<PageData> { + url = Location.fromUrl(url).url; + + const { visit = this.visit } = options; + const headers = { ...this.options.requestHeaders, ...options.headers }; + const timeout = options.timeout ?? this.options.timeout; + const controller = new AbortController(); + const { signal } = controller; + options = { ...options, headers, signal }; + + let timedOut = false; + let timeoutId: ReturnType<typeof setTimeout> | null = null; + if (timeout && timeout > 0) { + timeoutId = setTimeout(() => { + timedOut = true; + controller.abort('timeout'); + }, timeout); + } + + // Allow hooking before this and returning a custom response-like object (e.g. custom fetch implementation) + let response: Response; + try { + response = await this.hooks.call( + 'fetch:request', + visit, + { url, options }, + (visit, { url, options }) => fetch(url, options) + ); + if (timeoutId) { + clearTimeout(timeoutId); + } + } catch (error) { + if (timedOut) { + this.hooks.call('fetch:timeout', visit, { url }); + throw new FetchError(`Request timed out: ${url}`, { url, timedOut }); + } + if ((error as Error)?.name === 'AbortError' || signal.aborted) { + throw new FetchError(`Request aborted: ${url}`, { url, aborted: true }); + } + throw error; + } + + const { status, url: responseUrl } = response; + const html = await response.text(); + + if (status === 500) { + this.hooks.call('fetch:error', visit, { status, response, url: responseUrl }); + throw new FetchError(`Server error: ${responseUrl}`, { status, url: responseUrl }); + } + + if (!html) { + throw new FetchError(`Empty response: ${responseUrl}`, { status, url: responseUrl }); + } + + // Resolve real url after potential redirect + const { url: finalUrl } = Location.fromUrl(responseUrl); + const page = { url: finalUrl, html }; + + // Write to cache for safe methods and non-redirects + if (visit.cache.write && (!options.method || options.method === 'GET') && url === finalUrl) { + this.cache.set(page.url, page); + } + + return page; +} |
