@jcoder-stack/abp-react 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/proxy.d.ts CHANGED
@@ -4,11 +4,18 @@ import { A as Auth, d as OidcStrategy, b as Codec, C as CookieOptions } from './
4
4
  import { A as ApplicationConfiguration } from './application-configuration-DhOZRqtz.js';
5
5
  import { z } from 'zod';
6
6
 
7
+ /**
8
+ * 请求正文。四种形状的共同点是**可重发**:401→刷新→重放与幂等重试都要把同一个 body 再发一次。
9
+ *
10
+ * `ReadableStream` 刻意不在其中。它只能消费一次,收下它会让上述两条路径静默退化成「重放一个
11
+ * 空正文」——上游看到的是内容缺失的请求而不是错误,最难查。要传流请先自行缓冲成字节。
12
+ */
13
+ type AbpProxyBody = string | Uint8Array | ArrayBuffer | FormData;
7
14
  interface AbpProxyRequest {
8
15
  path: string;
9
16
  method?: string;
10
17
  headers?: Record<string, string>;
11
- body?: string;
18
+ body?: AbpProxyBody;
12
19
  /** 调用方的取消信号(如宿主的 `request.signal`);触发后当前尝试立即中止且不再重试。 */
13
20
  signal?: AbortSignal;
14
21
  }
@@ -49,6 +56,8 @@ declare function createAbpProxy(opts: {
49
56
  };
50
57
  /** 含重试与退避在内的总预算;默认不设,此时最坏耗时是 (retries+1)×timeoutMs 加退避。 */
51
58
  totalTimeoutMs?: number;
59
+ /** 可测正文(字节/FormData)的上限,默认 10MB。字符串正文不参与判断,见 `measurableBodyBytes`。 */
60
+ maxBodyBytes?: number;
52
61
  logger?: Logger;
53
62
  }): AbpProxy;
54
63
 
@@ -224,4 +233,4 @@ type InstallExtraCaResult = "installed" | "already-installed" | "unsupported";
224
233
  */
225
234
  declare function installExtraCa(caFile: string, caApi?: RuntimeCaApi): InstallExtraCaResult;
226
235
 
227
- export { type AbpAuthEnv, type AbpAuthRuntimeOptions, type AbpCallRuntime, type AbpProxy, type AbpProxyAuth, AbpProxyError, type AbpProxyRequest, type AbpProxyResponse, type AppState, type AuthCookieConfig, type AuthCookieSettings, type AuthRuntime, CULTURE_COOKIE, DEFAULT_LOGIN_COOKIE, DEFAULT_LOGIN_COOKIE_MAX_AGE, DEFAULT_SESSION_COOKIE, DEFAULT_SESSION_COOKIE_MAX_AGE, type InstallExtraCaResult, SWITCH_COOKIE_MAX_AGE, TENANT_COOKIE, abpAuthEnvSchema, buildPolicyHeaders, callAbpWithSession, cookieAttributesOf, createAbpAuthRuntime, createAbpIdentityResolver, createAbpProxy, deriveIdentity, handleCallback, handleLogin, handleLogout, handleSetCulture, handleSetTenant, installExtraCa, loadAppState, resolveAbpAuthEnv, tlsTrustFailureCode, tlsTrustFailureMessage, upstreamUnreachableCode, upstreamUnreachableMessage };
236
+ export { type AbpAuthEnv, type AbpAuthRuntimeOptions, type AbpCallRuntime, type AbpProxy, type AbpProxyAuth, type AbpProxyBody, AbpProxyError, type AbpProxyRequest, type AbpProxyResponse, type AppState, type AuthCookieConfig, type AuthCookieSettings, type AuthRuntime, CULTURE_COOKIE, DEFAULT_LOGIN_COOKIE, DEFAULT_LOGIN_COOKIE_MAX_AGE, DEFAULT_SESSION_COOKIE, DEFAULT_SESSION_COOKIE_MAX_AGE, type InstallExtraCaResult, SWITCH_COOKIE_MAX_AGE, TENANT_COOKIE, abpAuthEnvSchema, buildPolicyHeaders, callAbpWithSession, cookieAttributesOf, createAbpAuthRuntime, createAbpIdentityResolver, createAbpProxy, deriveIdentity, handleCallback, handleLogin, handleLogout, handleSetCulture, handleSetTenant, installExtraCa, loadAppState, resolveAbpAuthEnv, tlsTrustFailureCode, tlsTrustFailureMessage, upstreamUnreachableCode, upstreamUnreachableMessage };
package/dist/proxy.js CHANGED
@@ -260,6 +260,7 @@ var AbpProxyError = class extends Error {
260
260
  };
261
261
  var IDEMPOTENT = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
262
262
  var isRetryableStatus = (status) => status >= 500 || status === 429;
263
+ var utf8KeepingBom = new TextDecoder("utf-8", { ignoreBOM: true });
263
264
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
264
265
  var discardBody = (res) => res.body?.cancel().catch(() => {
265
266
  });
@@ -289,12 +290,28 @@ function exposeHeaders(headers) {
289
290
  }
290
291
  return out;
291
292
  }
292
- function sanitizeHeaders(headers) {
293
+ function sanitizeHeaders(headers, body) {
293
294
  if (headers === void 0) return {};
295
+ const dropContentType = typeof FormData !== "undefined" && body instanceof FormData;
294
296
  return Object.fromEntries(
295
- Object.entries(headers).filter(([key]) => FORWARDABLE.has(key.toLowerCase()))
297
+ Object.entries(headers).filter(
298
+ ([key]) => FORWARDABLE.has(key.toLowerCase()) && !(dropContentType && key.toLowerCase() === "content-type")
299
+ )
296
300
  );
297
301
  }
302
+ var DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
303
+ var isStreamLike = (body) => typeof body?.getReader === "function";
304
+ function measurableBodyBytes(body) {
305
+ if (body === void 0 || typeof body === "string") return null;
306
+ if (body instanceof ArrayBuffer) return body.byteLength;
307
+ if (ArrayBuffer.isView(body)) return body.byteLength;
308
+ let total = 0;
309
+ body.forEach((value, name) => {
310
+ total += name.length;
311
+ total += typeof value === "string" ? value.length : value.size;
312
+ });
313
+ return total;
314
+ }
298
315
  var ABSOLUTE_OR_PROTOCOL_RELATIVE = /^([a-z][a-z0-9+.-]*:)?\/\//i;
299
316
  function resolveTargetUrl(path, baseUrl) {
300
317
  if (ABSOLUTE_OR_PROTOCOL_RELATIVE.test(path)) {
@@ -312,8 +329,18 @@ function createAbpProxy(opts) {
312
329
  const fetchFn = opts.fetchFn ?? fetch;
313
330
  const timeoutMs = opts.timeoutMs ?? 3e4;
314
331
  const retries = opts.retry?.retries ?? 2;
332
+ const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
315
333
  return {
316
334
  async send(req, auth) {
335
+ if (isStreamLike(req.body)) {
336
+ throw new Error(
337
+ "abp proxy: a ReadableStream body cannot be replayed after a 401 refresh or an idempotent retry; buffer it into bytes first"
338
+ );
339
+ }
340
+ const bodyBytes = measurableBodyBytes(req.body);
341
+ if (bodyBytes !== null && bodyBytes > maxBodyBytes) {
342
+ throw new Error(`abp proxy: request body too large (${bodyBytes} > ${maxBodyBytes} bytes)`);
343
+ }
317
344
  const method = (req.method ?? "GET").toUpperCase();
318
345
  const maxRetries = IDEMPOTENT.has(method) ? retries : 0;
319
346
  const url = resolveTargetUrl(req.path, opts.baseUrl);
@@ -336,9 +363,12 @@ function createAbpProxy(opts) {
336
363
  res = await fetchFn(url, {
337
364
  method,
338
365
  headers: {
339
- ...sanitizeHeaders(req.headers),
366
+ ...sanitizeHeaders(req.headers, req.body),
340
367
  ...session === null ? {} : { Authorization: `Bearer ${session.tokens.accessToken}` }
341
368
  },
369
+ // AbpProxyBody 的四种形状运行时都是合法的 fetch 正文。TS 5.7 起 Uint8Array 带上了
370
+ // ArrayBufferLike 泛型参数,而 BodyInit 只认 ArrayBuffer 背衬的那支;把泛型参数写进
371
+ // 公开类型能消掉这次转换,但会反过来拒掉调用方最常写的裸 `Uint8Array` 标注。
342
372
  body: req.body,
343
373
  signal: AbortSignal.any([...stops, AbortSignal.timeout(timeoutMs)])
344
374
  });
@@ -385,7 +415,7 @@ function createAbpProxy(opts) {
385
415
  return {
386
416
  status: res.status,
387
417
  headers: exposeHeaders(res.headers),
388
- body: isText ? await res.text() : await res.arrayBuffer(),
418
+ body: isText ? utf8KeepingBom.decode(await res.arrayBuffer()) : await res.arrayBuffer(),
389
419
  setCookies
390
420
  };
391
421
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jcoder-stack/abp-react",
3
3
  "description": "Pure-React runtime for ABP backends: logging, ABP types with zod parsing, fetch client, auth sessions, the ABP proxy gateway, permissions, i18n, React providers/hooks, and TanStack Router guards — exported per domain via subpaths",
4
- "version": "0.2.1",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {