@i.un/api-client
v1.0.2
Published
Universal API client for i.un services
Downloads
398
Maintainers
Readme
Api Client
一个基于 ofetch 的轻量级 HTTP 客户端,内置:
- 自动携带
Authorizationtoken - 自动刷新 token(可选,串行防抖)
- 统一成功/失败返回结构(可配置协议)
- 完整 TypeScript 类型支持
适用于浏览器、Node、浏览器扩展等多种环境。
安装
npm install @i.un/api-client
# or
pnpm add @i.un/api-client
# or
yarn add @i.un/api-client包名:
@i.un/api-client。
快速上手
import { createApiClient, type TokenStorage } from "@i.un/api-client";
// 1. 提供一个 TokenStorage(决定 token 从哪里来、存到哪里去)
const tokenStorage: TokenStorage = {
async getAccessToken() {
return localStorage.getItem("access_token") || "";
},
async setAccessToken(token: string) {
localStorage.setItem("access_token", token);
},
};
// 2. 创建 client
const client = createApiClient({
baseURL: "https://api.example.com",
tokenStorage,
// 刷新 token 的接口(默认 ApiResult 协议:{ code, data: { access_token }, message })
refreshToken: "/auth/refresh",
});
// 3. 使用
const { get, post, put, patch, del, request } = client;
// 示例:GET
const user = await get<{ name: string }>("/user/profile");
// 示例:POST
const updated = await post<{ ok: boolean }>("/user/profile", { name: "foo" });默认响应协议
库内置了一个默认响应协议类型:
export interface ApiResult<T> {
code: number;
data: T;
message: string;
}默认行为:
- 约定后端统一返回
ApiResult<T>。 - 调用
request/get/post/...:- 当
code === 0:- 默认返回
data(即T)。 - 如果传了
returnFullResponse: true,则返回完整的ApiResult<T>。
- 默认返回
- 当
code !== 0:- 抛出
ApiError(自定义错误类型,见下文)。
- 抛出
- 当
你可以通过配置项覆盖这套协议(见「高级:自定义解包和错误映射」)。
错误类型
export interface ApiError extends Error {
code: number; // 业务错误码
data?: unknown; // 后端 data 字段
status?: number; // HTTP 状态码(仅网络层错误时可能存在)
}
export const isApiError = (error: unknown): error is ApiError => {
return error instanceof Error && "code" in error;
};调用方使用示例:
try {
const data = await get("/some/api");
} catch (e) {
if (isApiError(e)) {
console.log("business error", e.code, e.message, e.data);
} else {
console.error("unexpected error", e);
}
}配置项:CreateApiClientOptions
export interface CreateApiClientOptions {
baseURL: string;
tokenStorage: TokenStorage;
// 自动刷新 token(可选)
refreshToken?: (() => Promise<string>) | string | false;
// 识别“需要刷新 / 认证失败”的业务错误(可选,默认 code === 401)
isAuthError?: (code: number) => boolean;
// 自定义成功响应解包逻辑(可选)
unwrapResponse?<T>(result: unknown, returnFullResponse: boolean): T;
// 自定义失败响应映射为 Error 的逻辑(可选)
createErrorFromResult?(res: unknown): Error;
}TokenStorage
export interface TokenStorage {
getAccessToken: () => Promise<string> | string;
setAccessToken: (token: string) => Promise<void> | void;
}你可以自由决定 token 存到哪里,比如:
- 浏览器:
localStorage/sessionStorage/cookie - Node:内存变量、Redis、数据库等
- 浏览器扩展:
chrome.storage、cookies等
自动刷新 Token
方式一:字符串 endpoint(推荐)
const client = createApiClient({
baseURL,
tokenStorage,
refreshToken: "/auth/refresh",
});内部行为:
- 业务请求返回
isAuthError(code) === true(默认 401)时:- 调用
ofetch<ApiResult<{ access_token: string }>>(refreshToken, { baseURL, method: "POST" })。 - 如果
code !== 0,使用createErrorFromResult抛错。 - 如果
code === 0,从data.access_token取出新 token,并调用tokenStorage.setAccessToken存储。 - 然后自动使用新 token 重试原请求一次。
- 调用
- 串行防抖:
- 使用内部的
refreshingPromise确保同一时刻只有一个刷新请求在进行; - 其它并发 401 请求会等待这次刷新完成,复用刷新结果。
- 使用内部的
方式二:自定义函数
const client = createApiClient({
baseURL,
tokenStorage,
refreshToken: async () => {
const res = await ofetch<{ accessToken: string }>("/auth/refresh", {
baseURL,
method: "POST",
});
return res.accessToken;
},
});建议在
refreshToken内部不要再调用同一个带自动刷新的request,以避免在刷新接口也返回认证错误时形成递归。
请求方法
const { rawRequest, request, get, post, put, patch, del } = createApiClient(...);rawRequest: 底层的$fetch实例(来自ofetch.create),按原样返回结果,不做自动刷新和解包。request<T>(url, options?): 核心方法,自动携带 token / 自动刷新 / 解包 / 抛Error。get<T>(url, params?, options?):GET请求,params会被放到query。post/put/patch<T>(url, body?, options?):body默认{}。del<T>(url, params?, options?):DELETE请求,params放到query。
示例:
// GET /users?id=1
const user = await get<User>("/users", { id: 1 });
// POST /users { name }
const created = await post<User>("/users", { name: "foo" });
// 直接用 request(更通用)
const data = await request<User>("/users/1", {
method: "GET",
returnFullResponse: false, // true 时返回 ApiResult<User>
});高级:自定义解包和错误映射
如果你的后端协议不是 { code, data, message },可以通过配置覆盖:
const client = createApiClient({
baseURL,
tokenStorage,
refreshToken: false,
unwrapResponse<T>(result, returnFullResponse) {
// 举例:后端协议是 { success, result, errorMsg }
if (result && typeof result === "object" && "success" in result) {
const body = result as any;
if (body.success) {
return returnFullResponse ? (body as T) : (body.result as T);
}
}
return result as T;
},
createErrorFromResult(res): Error {
const body = res as any;
const err = new Error(body.errorMsg || "Request failed");
// 可以按需挂一些自定义字段,比如 err.code / err.raw
return err;
},
});环境支持与注意事项
- 依赖
ofetch,可在浏览器、Node 18+、Nuxt 等环境使用。 - 需要环境有
fetch/Headers:- 浏览器:原生支持。
- Node 18+:内置
fetch。 - 更低版本 Node:需要自行 polyfill(例如
undici或cross-fetch)。
client.ts本身不依赖window/localStorage/chrome等对象,这些应在你实现TokenStorage时根据环境自行选择。
License
MIT
