Untitled

 avatar
unknown
javascript
10 months ago
4.8 kB
7
Indexable
import { error, type NumericRange } from "@sveltejs/kit"
import Cookie from "cookie"

import { setCookie } from "./cookie"

import { isDev } from "~config"

type RecursiveUrlParams = { [key: string]: RecursiveUrlParams | string | boolean | unknown }

interface FetchOptions {
	method?: "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "HEAD" | "PATCH"
	headers?: Record<string, string>
	body?: BodyInit | null | undefined
	data?: unknown
	mode?: "cors" | "no-cors" | "same-origin"
	credentials?: "include" | "omit" | "same-origin"
	withCredentials?: boolean
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	params?: Record<string, RecursiveUrlParams | string> | any
	cache?: "default" | "no-store" | "reload" | "no-cache" | "force-cache" | "only-if-cached"
	redirect?: "follow" | "error" | "manual"
	referrer?: string
	referrerPolicy?:
		| "no-referrer"
		| "no-referrer-when-downgrade"
		| "origin"
		| "origin-when-cross-origin"
		| "same-origin"
		| "strict-origin"
		| "strict-origin-when-cross-origin"
		| "unsafe-url"
	integrity?: string
	keepalive?: boolean
	signal?: AbortSignal | null
	baseURL?: string
	url?: string
	fetch?: typeof fetch
}

export const isObject = (value: unknown): value is Record<string, unknown> => {
	return typeof value === "object" && value !== null && !Array.isArray(value)
}

export const encodeQueryParams = (params: Record<string, unknown>): string => {
	const customEncodeURIComponent = (str: string): string => {
		return encodeURIComponent(str)
			.replace(/[!'()*~]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)
			.replace(/%20/g, "%20")
	}

	const encode = (key: string, value: unknown): string => {
		if (value === undefined) {
			return ""
		}
		if (Array.isArray(value)) {
			if (value.length === 0) {
				return `${customEncodeURIComponent(key)}%5B%5D=`
			}
			return value
				.map((v, i) => encode(`${key}[${i}]`, v))
				.filter(Boolean)
				.join("&")
		}
		if (isObject(value)) {
			return Object.entries(value)
				.map(([k, v]) => encode(`${key}[${k}]`, v))
				.filter(Boolean)
				.join("&")
		}
		return `${customEncodeURIComponent(key)}=${customEncodeURIComponent(String(value))}`
	}

	return Object.entries(params)
		.map(([k, v]) => encode(k, v))
		.filter(Boolean)
		.join("&")
}

export async function fetcher<T>(options: FetchOptions = {}) {
	const defaultOptions = {
		method: "GET",
	} as FetchOptions
	let response
	if (["POST", "PUT"].includes(options.method || "") && !options.headers?.["Content-Type"]) {
		defaultOptions.headers = {
			"Content-Type": "application/json",
		}
	}

	const mergedOptions = {
		// credentials: "include" as RequestCredentials,
		...defaultOptions,
		...options,
		headers: {
			...defaultOptions.headers,
			...options.headers,
		},
	}
	// Map withCredentials to the fetch credentials option
	if (options.withCredentials) {
		mergedOptions.credentials = "include"
	}

	if (options.data) {
		if (options.headers?.["Content-Type"] === "application/x-www-form-urlencoded") {
			const formBody = []
			for (const [key, value] of Object.entries(options.data)) {
				const encodedKey = encodeURIComponent(key)
				const encodedValue = encodeURIComponent(value as string)
				formBody.push(encodedKey + "=" + encodedValue)
			}
			mergedOptions.body = formBody.join("&")
		} else {
			mergedOptions.body = JSON.stringify(options.data)
		}
		delete mergedOptions.data
	}

	const urlObject = new URL(`${options.baseURL || ""}${options.url || ""}`)

	if (options.params) {
		const queryString = encodeQueryParams(options.params as Record<string, unknown>)
		urlObject.search = queryString
	}

	const fullUrl = urlObject.toString()

	try {
		const fetchingHandler = isDev ? fetch : options?.fetch || fetch

		response = await fetchingHandler?.(fullUrl, mergedOptions)
		const setCookieHeader = response.headers.get("Set-Cookie")
		try {
			if (setCookieHeader) {
				const parsedCookie = Cookie.parse(setCookieHeader)
				if (parsedCookie.id_token) setCookie("id_token", parsedCookie.id_token)
				if (parsedCookie.access_token) setCookie("access_token", parsedCookie.access_token)
			}
		} catch (error) {
			console.log("setCookieHeader error :>> ", error)
		}

		if (+response?.status >= 400) {
			const errorData = await response?.json()
			console.log("errorData :>> ", fullUrl, errorData)
			error(response?.status as NumericRange<400, 599>, errorData || "Fetch error")
		}

		const data = (await response?.json()) as T

		return { data } as { data: T }
	} catch (err: unknown) {
		console.error("Fetch error:", fullUrl, JSON.stringify(err))
		// console.error("error response", response);
		let status
		if (response?.status) status = +response?.status < 400 ? 400 : +response?.status
		error(
			(status as NumericRange<400, 599>) || 500,
			((err as ErrorEvent).message || err || "Fetch error") as Error,
		)
	}
}
Editor is loading...
Leave a Comment