@cedvict/http-guardian (中文(简体) )
HTTP 核心客户端( 承诺) fetch。 。 。 .
安装
运行时间要求 : 节点 第22页 (或任何现代浏览器/更新运行时间) fetch) (中文(简体) ).
如果你在一个没有全局的环境运行 fetch,明确通过 createHttpClient({ fetch })。 。 。 .
释放(0.0.1-next.*发表于 next dis. 直至a
稳定版本已出炉, latest 指向更旧的预放:从
next,或者将准确的版本标出.
npm i @cedvict/http-guardian@next
快例( REST 经典信封)
import { createHttpClient, bearerAuth, createApiParserRestClassic, memoryCache } from "@cedvict/http-guardian";
const api = createHttpClient({
baseUrl: "https://api.example.com",
auth: bearerAuth(() => localStorage.getItem("token")),
parser: createApiParserRestClassic(),
cache: memoryCache({ maxEntries: 500 })
});
const res = await api.get<{ id: string; name: string }>("/me", {
cache: { ttlMs: 30_000, tags: ["me"] }
});
if (res.ok) console.log(res.data.name);
else console.error(res.errors);
二进制回复
使用明确的响应类型进行下载. 成功响应绕过 JSON信封解析器 他们的字节保持不变 错误响应仍然使用 普通内容类型解码器,它保存结构化的 API 错误.
const report = await api.get<Blob>("/reports/monthly", {
responseType: "blob",
headers: { accept: "application/pdf" },
});
if (report.ok) {
const url = URL.createObjectURL(report.data);
// Open or download the URL, then call URL.revokeObjectURL(url).
} else {
console.error(report.errors);
}
responseType: "arrayBuffer" 还可用于以字节为导向的处理.
二进制请求未收到默认 Accept: application/json 标题,
并保存缓存/调用密钥 blob 财务报告和审定财务报表 arrayBuffer 答复分开.
预设
createApiParserRestClassic()createApiParserLaravel()createApiParserNest()createApiParserGraphQL({ allowPartialData? })createApiParserStructured({ exposeMeta?, honorRetryableHint? })- 解析器 帮助打字:{ success, code, message, details, retryable, issues[], i18n, meta }。 。 。 . {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}那又怎样AppError端口code, (中文(简体) ),message和布伦打字retryable后台和冠军category, (中文(简体) ),severity, (中文(简体) ),i18n, (中文(简体) ),traceId, (中文(简体) ),spanId, (中文(简体) ),envelope) 不安不负责任details. 发布 peut 重置器retryable(原始内容存档于2017-09-21). de l'enveloppe.createApiParserProblem({ codeMembers?, requestIdHeader? })- 问题细节 (RFC 9457,application/problem+json) (中文(简体) ). 来自detail,则title· ;code从第一个非空白成员codeMembers(默认)["code"], (中文(简体) ), 譬如说.["rule", "code"]);每个扩展保存在details.extensions(单位:千美元)ProblemErrorDetails),与retryAfter(单位:千美元)Retry-After标题)和requestId布尔retryable成员变成AppError.retryable。 。 。 . 验证问题给出一个AppError每个输入,从errors(单位:千美元){ detail, pointer })或(或)invalid-params(单位:千美元){ name, reason }) (中文(简体) ).createApiParserDetail({ requestIdHeader? })——API不成功 信封:成功体是数据,错误是{ detail, code?, retryAfter?, requestId? }。 。 。 . 字符串detail给一个AppError(单位:千美元)code保留);422项验证detail数组给出一个AppError每个输入field(单位:千美元)loc无 联合国body编号 :query/...来源)和code(议题)type) (中文(简体) ).details(单位:千美元)DetailErrorDetails载体retryAfter(单位:千美元)Retry-After标题, 延迟或 HTTP 日期,否则身体字段,以整秒计),requestId(躯体,其他)X-Request-ID, (中文(简体) ),loc和生的body。 。 。 .
解析器可以读取一个失败的响应的页眉 : getErrors(raw, status, response?) 接收 { headers } 作为可选的第三个论据.
没有 notifier,客户端使用 consoleNotifier,它每写
失败到控制台。 过 silentNotifier 当应用程序显示时
自有信件来源 result.errors。 。 。 .
Auth 模式
bearerAuth(() => token)cookieAuth({ credentials: "include", csrf: { headerName, getToken } })noAuth()
警卫
守卫在无中相器械中.
bearerRefreshGuard({ refreshFn, onAuthFailed?, ... })
刷新 JWT 显示器 单机 : sur 401, 无人知晓 refreshFn() 时间轴:;
{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}401 reçues pendant sce sreaking sententent dans une 队列 存档副本
(原始内容存档于2013-10-10). Partagée puis sont rejouées avec le nouveau sord. 刷新音乐,
onAuthFailed(error) 排队时的法语和法语
(原始内容存档于2017-10-21) (英语). vorient le 401 d'grounde.
import { createHttpClient, bearerAuth, bearerRefreshGuard } from "@cedvict/http-guardian";
createHttpClient({
auth: bearerAuth(() => tokenStore.access()),
guards: [
bearerRefreshGuard({
refreshFn: async () => {
const { accessToken, refreshToken } = await api.refresh();
tokenStore.set(accessToken, refreshToken);
return { accessToken, refreshToken };
},
onAuthFailed: () => router.navigate("/login"),
skipRefreshFor: (ctx) => ctx.url.includes("/auth/"),
}),
],
});
csrfGuard({ tokenFromCookie?, tokenFromStorage?, tokenFromHeader?, headerName?, applyTo? })
弹出去头 X-CSRF-Token (défaut) 变种动物
(单位:千美元)POST编号 :PUT编号 :PATCH编号 :DELETE) (中文(简体) ). 安保部门安全 页:1 tokenFromCookie 使用率最高
浏览器( r)typeof document === 'undefined'),le guard shippe silencieusement. (原始内容存档于2018-09-31).
csrfGuard(
isPlatformBrowser(platformId)
? { tokenFromCookie: "csrf_token" }
: { tokenFromStorage: () => null },
);
correlationIdGuard({ headerName?, generator?, readResponseHeader? })
弹出去头 X-Correlation-Id (UUID v4 par défaut,倒计时)
Math.random()+时间戳 si crypto.randomUUID 页:1 (原始内容存档于2018-09-21) (英语). L'ID est 稳定
请求重试 (法语). (原始内容存档于2018-09-26) (英语). Si la réponse contient le même header,
仅此而已 ctx.meta.correlationId 下游插件倾斜
(伐木工,哨兵,奥特尔).
💡 模式“ X- request-Id ” : si ton后端(ops, ELK, Datadog...) 使用 le 页眉
X-Request-Idplut-t que (英语)X-Correlation-Id倒接缝线 日志前/后端, 更改正则headerName数字 :correlationIdGuard({ headerName: "X-Request-Id" })(原始内容存档于2018-10-21) (英语). Pas besoin d'OpenTelemetery côté front, ni d'un 二等后卫: C'est le même (原始内容存档于2012-10-12). Mécanisme avec un nom différent. 可组合的avec un OTel
traceparent数字 : {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}什么? {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}什么.
idempotencyGuard({ headerName?, applyTo?, respectExisting? })
弹出去头 X-Idempotency-Key (乌伊德·德福特) POST编号 :PUT编号 :PATCH。 。 。 .
拉克莱斯特 稳定企业重试 ——活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活.
尊重 une Clé 四分卫通过 RequestOptions.idempotencyKey, (中文(简体) ),
ctx.idempotencyKey,通过头来指导.
食谱:映射器 le traceId 后端 QQ 哨兵/ OTel 前端
Quand le 后端 renvoie une enveloppe avec un 页面存档备份,存于互联网档案馆 meta.traceId 页:1 spanId(中文(简体) )
- 通过类型 createApiParserStructured 缝纫机用具
追踪空地/活动前端的克恩事件
vers la trace后端对应(et 反向).
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}蓝色的胸口
correlationIdGuard格内尔·恩X-Correlation-Id支持恢复, 和lit le header de réponse pour exploser l'ID dans 相接(英文)ctx.meta.correlationId。 。 。 .createApiParserStructured储存traceId编号 :spanId达斯errors[].details(en cas d'erreur) (法语). 圣克萨斯圣克萨斯圣克萨斯 通过无宠卫士 dédié.
import {
createHttpClient,
bearerAuth,
correlationIdGuard,
createApiParserStructured,
type Guard,
} from "@cedvict/http-guardian";
import { sentryPlugin } from "@cedvict/http-guardian-plugin-sentry";
import { otelPlugin } from "@cedvict/http-guardian-plugin-otel";
import * as Sentry from "@sentry/browser";
// Lit X-Trace-Id / X-Span-Id de la réponse et les pose sur ctx.meta pour
// les plugins downstream (sentry, otel, logger).
const traceMetaGuard: Guard = async (ctx, next) => {
const res = await next(ctx);
const traceId = res.headers.get("x-trace-id");
const spanId = res.headers.get("x-span-id");
if (traceId || spanId) {
ctx.meta ??= {};
if (traceId) ctx.meta.traceId = traceId;
if (spanId) ctx.meta.spanId = spanId;
}
return res;
};
const api = createHttpClient({
baseUrl: "https://api.example.com",
auth: bearerAuth(() => token),
parser: createApiParserStructured(),
guards: [correlationIdGuard(), traceMetaGuard],
plugins: [
sentryPlugin({
// shouldCapture reçoit le ctx → on lit ctx.meta pour tagger l'event Sentry.
shouldCapture: ({ status, ctx }) => {
if (status && status >= 500) {
Sentry.getCurrentScope().setTags({
"backend.trace_id": ctx.meta?.traceId ?? "",
correlation_id: ctx.meta?.correlationId ?? "",
});
return true;
}
return false;
},
}),
otelPlugin({
setAttributes: (span, ctx) => {
if (ctx.meta?.traceId) span.setAttribute("backend.trace_id", ctx.meta.traceId);
if (ctx.meta?.spanId) span.setAttribute("backend.span_id", ctx.meta.spanId);
if (ctx.meta?.correlationId) span.setAttribute("correlation_id", ctx.meta.correlationId);
},
}),
],
});
科特错失,我被贬 createApiParserStructured 揭发déjà
traceId 编号 : spanId 达斯 result.errors[0].details 数字 :
const r = await api.post("/orders", { json: payload });
if (!r.ok) {
const d = r.errors[0]?.details as { traceId?: string; spanId?: string } | undefined;
console.error("backend trace:", d?.traceId, d?.spanId);
}
重定向
redirects: { mode: "follow" | "manual" | "error", maxHops, onRedirect }
测试
见 TESTING.md。 。 。 .
Cookie 会话刷新后卫(单飞、冷却、反跳动)
如果您使用基于 cookie 的 auuth , 而您的后端会显示一个更新的端点, 您可以添加安全更新的 guard :
import { createHttpClient, cookieAuth, cookieSafeRefreshGuard, createApiParserRestClassic } from "@cedvict/http-guardian";
const api = createHttpClient({
baseUrl: "https://api.example.com",
auth: cookieAuth({ credentials: "include" }),
parser: createApiParserRestClassic(),
guards: [
cookieSafeRefreshGuard({
refreshPath: "/auth/refresh", // or absolute: "https://auth.example.com/refresh"
maxRetry: 1, // anti-loop (default)
honorRetryAfter: true // cooldown on 429 (default)
})
],
});
您可以按请求禁用任何认证/ 新鲜行为 noAuth: true数字 :
await api.get("/public/health", { noAuth: true });
通知编辑( 可选)
如果您提供了自定义通知符并记录/发送事件到遥测,您可以添加一个 redact 函数
从事件有效载荷中去除秘密(例如: url:
import type { Notifier, NotifyEvent } from "@cedvict/http-guardian";
const notifier: Notifier = {
redact: (e: NotifyEvent) => ({ ...e, url: e.url.replace(/\?.*$/, "") }),
notify: (e) => console.error(e),
};
预设构建器和别名( sable DX)
如果您想要一个1行设置而不失去访问权限 createHttpClient,使用预设构建器 :
import { createPresetBuilder } from "@cedvict/http-guardian";
const api = createPresetBuilder({ baseUrl: "https://api.example.com" })
.cookie({ credentials: "include" })
// opt-in only:
// .cookieRefresh({ refreshPath: "/auth/refresh", mePath: "/auth/me" })
.create();
异名( 围绕预设构建器的薄膜):
createCookieClient({ baseUrl, refresh?: CookieSafeRefreshGuardOptions | null, ... })createBearerClient({ baseUrl, getToken, ... })
稳定认证刷新策略
- Cookie 认证: 刷新为 选入 (编)
cookieRefresh(...)或通过refresh改为createCookieClient) (中文(简体) ). - 持有者认证: 刷新为 默认禁用。 。 。 .
插件( 配置时间)
plugins 被同步应用于 createHttpClient(...) 以更改选项草案对象.
合同:
- 应用 顺序
- 由
name(第一胜负)