import type { HttpEngine } from './engine/engine' export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' /** 请求客户端配置 */ export interface ClientConfig { /** * 源 * - 由协议、主机名(域名)和端口定义。 * - 如果未指定,默认为当前页面的`location.href`。 * - 如果 `url` 已经是一个完整的[URL](https://developer.mozilla.org/zh-CN/docs/Web/API/URL) ,则会忽略此配置。 */ origin?: string /** * 请求超时时间 (单位:ms) * - 默认为0,即不超时 * - 设置超时时间后,请求会在指定时间后自动终止,并抛出408错误。 * - 除了上传下载文件,通常情况下,你的普通请求不应该很长。 */ timeout?: number /** * HTTP默认请求头 * - 这里指定的是实例请求头配置。 * - 如果在请求配置中传入了`headers`,则header会被合并,相同的header会被请求配置覆盖。 */ headers?: Record /** * 控制`浏览器`是否发送凭证(Cookie / HTTP 认证 / TLS 客户端证书) * - 默认发送,即跨域请求也会携带凭证。 * - 如果设置为`false`,则在任何请求时都不会发送凭证。 * @default true */ credentials?: boolean /** * 插件 * - 插件是一种扩展机制,可以用于修改请求和响应。 */ plugins?: HTTPClientPlugin[] /** * 自定义请求引擎 * - 未传入时,自动选择 FetchEngine(全局 fetch 可用)或 XHREngine * - 可传入自定义 HttpEngine 子类实例以对接其他底层(如 undici、msw mock) */ engine?: HttpEngine /** * 响应类型 * - 'json': 解析为 JSON * - 'text': 解析为文本 * - 'blob': 解析为 Blob * - 'arraybuffer': 解析为 ArrayBuffer * - 未设置时根据响应 `Content-Type` 自动推断 */ responseType?: 'json' | 'text' | 'blob' | 'arraybuffer' /** 默认终止信号(可被单次请求的 `signal` 覆盖) */ signal?: AbortSignal /** 默认上传进度回调 */ onUploadProgress?: (info: ProgressInfo) => void /** 默认下载进度回调 */ onDownloadProgress?: (info: ProgressInfo) => void /** * XSRF Cookie 名称 * - 默认 `'XSRF-TOKEN'` * - 仅在浏览器环境且请求为同域时生效 */ xsrfCookieName?: string /** * XSRF Header 名称 * - 默认 `'X-XSRF-TOKEN'` */ xsrfHeaderName?: string } export interface RequestConfig { /** * 请求方法 * - 默认`GET` */ method?: RequestMethod /** * 请求体 * - ReadableStream数据在不支持fetch的环境下无效 * - JS对象会被自动转换为JSON字符串,并且会自动设置`Content-Type`为`application/json` * - `URLSearchParams` 会自动设置 `Content-Type: application/x-www-form-urlencoded` * - `FormData` 会直接作为 body,不自动设置 `Content-Type`(由浏览器设置 multipart boundary) */ body?: BodyInit | Record | URLSearchParams | FormData /** * 查询参数 * - 如果你在url中也指定了查询参数,那么它们会被合并。 * - 查询参数会被自动转换为`key=value`的形式 */ query?: Record /** * 请求头 * @link [查看](https://developer.mozilla.org/zh-CN/docs/Glossary/Request_header) */ headers?: Record /** 请求超时时间 */ timeout?: number /** 请求是否携带凭证 */ credentials?: boolean /** * 响应类型 * - 'json': 解析为 JSON * - 'text': 解析为文本 * - 'blob': 解析为 Blob * - 'arraybuffer': 解析为 ArrayBuffer * - 未设置时根据响应 `Content-Type` 自动推断 */ responseType?: 'json' | 'text' | 'blob' | 'arraybuffer' /** 用户传入的终止信号(per-request) */ signal?: AbortSignal /** 上传进度(Fetch 引擎下会被静默忽略) */ onUploadProgress?: (info: ProgressInfo) => void /** 下载进度(需引擎支持流式读取) */ onDownloadProgress?: (info: ProgressInfo) => void /** * XSRF Cookie 名称(覆盖 ClientConfig) * - 默认 `'XSRF-TOKEN'` */ xsrfCookieName?: string /** * XSRF Header 名称(覆盖 ClientConfig) * - 默认 `'X-XSRF-TOKEN'` */ xsrfHeaderName?: string /** @internal 内置重试逻辑写入的重试次数,业务代码勿依赖 */ _retryAttempt?: number } /** 传输进度信息 */ export interface ProgressInfo { /** 已传输字节数 */ loaded: number /** 总字节数,未知时为 0 */ total: number /** 进度百分比 0-100,total 为 0 时固定返回 0 */ percent: number } export type AliasRequestConfig = Omit export interface HTTPResponse { /** 响应体 */ body: T /** HTTP状态码 */ code: number /** 响应标头(set-cookie 多值以换行符分隔) */ headers: Record /** 原始响应对象 */ raw?: Response | any } export type HttpErrorCode = | 'TIMEOUT' | 'ABORTED' | 'NETWORK' | 'PARSE' | 'AUTH' | 'UNKNOWN' | 'RETRY_LIMIT_EXCEEDED' | 'PLUGIN' export interface HTTPErrorOptions { code: HttpErrorCode url?: string config?: RequestConfig response?: HTTPResponse cause?: unknown } export class HTTPError extends Error { code: HttpErrorCode url?: string config?: RequestConfig response?: HTTPResponse override cause?: unknown constructor(message: string, options: HTTPErrorOptions) { super(message) this.name = 'HTTPError' this.code = options.code this.url = options.url this.config = options.config this.response = options.response this.cause = options.cause } } export interface IHTTPClient { getEngine(): HttpEngine registerPlugin(plugin: HTTPClientPlugin): void request(url: string, config?: RequestConfig): Promise> get(url: string, config?: AliasRequestConfig): Promise> post( url: string, body?: RequestConfig['body'], config?: Omit ): Promise> put( url: string, body?: RequestConfig['body'], config?: Omit ): Promise> delete(url: string, config?: Omit): Promise> patch( url: string, body?: RequestConfig['body'], config?: Omit ): Promise> head(url: string, config?: Omit): Promise> options(url: string, config?: Omit): Promise> abort(): void group(prefix: string): IHTTPClient } export interface RequestContext { url: string config: RequestConfig } /** * 插件钩子返回类型 */ export interface PluginHookResult { /** 修改后的 URL */ url?: string /** 修改后的请求配置 */ config?: RequestConfig } interface BeforeRequestContext { /** 最终请求 URL */ url: string /** 最终请求配置 */ config: RequestConfig } interface AfterRespondContext { /** 响应对象 */ response: HTTPResponse /** 最终请求 URL */ url: string /** 最终请求配置 */ config: RequestConfig /** 调用 client.request / get 等方法时传入的原始 URL */ originalUrl: string /** 合并客户端默认值后、beforeRequest 前的原始配置 */ originalConfig: RequestConfig /** 请求客户端 */ client: IHTTPClient } /** 请求客户端插件 */ export interface HTTPClientPlugin { /** 插件名称 */ name: string /** * 请求前钩子 * @param url 请求 URL * @param config 请求配置 * @returns 修改后的 URL 和请求选项 */ beforeRequest?( context: BeforeRequestContext ): Promise | PluginHookResult | void /** * 响应后钩子 * @param context 响应后上下文 */ afterRespond?(context: AfterRespondContext): Promise | HTTPResponse | void /** * 错误钩子 * - 请求链中出现错误时触发 * - 返回 HTTPResponse 时视为错误已恢复,以首个非空返回为准;后续插件仍会执行 */ onError?( error: unknown, context: RequestContext ): Promise | HTTPResponse | void } export type ClientPlugin = HTTPClientPlugin