@lark-apaas/http-client 0.1.3-alpha.0 → 0.1.3-alpha.2

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/README.md CHANGED
@@ -421,6 +421,33 @@ const client = new HttpClient({
421
421
  });
422
422
  ```
423
423
 
424
+ ### 管理端 innerapi(仅用于开发态)
425
+
426
+ 妙搭开发态 innerapi 在网关侧配置了 admin 插件,会额外注入用户/Agent 身份。开启 `adminInnerApi` 后 SDK 会:
427
+
428
+ - 把 baseURL 改从 `MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX` 读取(不再回退到 `FORCE_AUTHN_INNERAPI_DOMAIN`)
429
+ - 每次请求时从 `MIAODA_AUTHN_CODE` 读取用户凭证,注入到请求头 `X-Miaoda-Client-Token`
430
+ - **AK/SK 的 `Authorization` 与 `x-api-key` 照旧注入**(`adminInnerApi` 只是叠加,不是替换)
431
+
432
+ ```typescript
433
+ const client = new HttpClient({
434
+ platform: {
435
+ enabled: true,
436
+ adminInnerApi: true, // 开启管理端 innerapi 链路
437
+ // adminDomainEnv: 'MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX', // 默认即此
438
+ // clientTokenEnv: 'MIAODA_AUTHN_CODE', // 默认即此
439
+ },
440
+ });
441
+
442
+ // 请求会发往 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX
443
+ // 请求头会带 Authorization / x-api-key / X-Miaoda-Client-Token
444
+ await client.get('/innerapi/v1/{bizDomain}/{path}');
445
+ ```
446
+
447
+ > `MIAODA_AUTHN_CODE` 由网关在运行时注入,初始化阶段不存在也不会校验。请求时若读不到,则不注入 `X-Miaoda-Client-Token` 头(其它请求头不受影响)。
448
+ >
449
+ > 管理端 innerapi 仅在妙搭开发态部署,运行态请勿开启 `adminInnerApi`。
450
+
424
451
  ## 安全特性
425
452
 
426
453
  ### 严格模式
@@ -593,6 +620,9 @@ interface HttpClientConfig extends RequestConfig {
593
620
  app_env?: string;
594
621
  sandbox_id?: string;
595
622
  };
623
+ adminInnerApi?: boolean; // 管理端 innerapi 链路(仅开发态)
624
+ adminDomainEnv?: string; // 管理端域名环境变量(默认 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX)
625
+ clientTokenEnv?: string; // 用户凭证环境变量(默认 MIAODA_AUTHN_CODE)
596
626
  };
597
627
  security?: SecurityConfig; // 安全配置
598
628
  }
package/dist/index.d.mts CHANGED
@@ -147,6 +147,17 @@ interface PlatformPluginOptions {
147
147
  expireTimeMs?: number;
148
148
  refreshBeforeMs?: number;
149
149
  defaultClaims?: PlatformClaims;
150
+ /**
151
+ * 管理端 innerapi 链路(仅用于妙搭开发态)
152
+ * 启用后:
153
+ * - baseURL 改从 adminDomainEnv(默认 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX)读取
154
+ * - 每次请求从 clientTokenEnv(默认 MIAODA_AUTHN_CODE)读取用户凭证,
155
+ * 注入为 X-Miaoda-Client-Token 请求头(读不到则不注入)
156
+ * - AK/SK 的 Authorization + x-api-key 照旧
157
+ */
158
+ adminInnerApi?: boolean;
159
+ adminDomainEnv?: string;
160
+ clientTokenEnv?: string;
150
161
  }
151
162
  interface PlatformRequestAuthConfig {
152
163
  customClaims?: PlatformClaims;
package/dist/index.d.ts CHANGED
@@ -147,6 +147,17 @@ interface PlatformPluginOptions {
147
147
  expireTimeMs?: number;
148
148
  refreshBeforeMs?: number;
149
149
  defaultClaims?: PlatformClaims;
150
+ /**
151
+ * 管理端 innerapi 链路(仅用于妙搭开发态)
152
+ * 启用后:
153
+ * - baseURL 改从 adminDomainEnv(默认 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX)读取
154
+ * - 每次请求从 clientTokenEnv(默认 MIAODA_AUTHN_CODE)读取用户凭证,
155
+ * 注入为 X-Miaoda-Client-Token 请求头(读不到则不注入)
156
+ * - AK/SK 的 Authorization + x-api-key 照旧
157
+ */
158
+ adminInnerApi?: boolean;
159
+ adminDomainEnv?: string;
160
+ clientTokenEnv?: string;
150
161
  }
151
162
  interface PlatformRequestAuthConfig {
152
163
  customClaims?: PlatformClaims;
package/dist/index.js CHANGED
@@ -203,6 +203,9 @@ var JWTTokenManager = class {
203
203
  var DEFAULT_DOMAIN_ENV = "FORCE_AUTHN_INNERAPI_DOMAIN";
204
204
  var DEFAULT_ACCESS_KEY_ENV = "FORCE_AUTHN_ACCESS_KEY";
205
205
  var DEFAULT_SECRET_KEY_ENV = "FORCE_AUTHN_ACCESS_SECRET";
206
+ var DEFAULT_ADMIN_DOMAIN_ENV = "MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX";
207
+ var DEFAULT_CLIENT_TOKEN_ENV = "MIAODA_AUTHN_CODE";
208
+ var MIAODA_CLIENT_TOKEN_HEADER = "X-Miaoda-Client-Token";
206
209
  function resolvePlatformBaseURL(options) {
207
210
  if (!options?.enabled) {
208
211
  return options?.baseURL;
@@ -210,7 +213,7 @@ function resolvePlatformBaseURL(options) {
210
213
  if (options.baseURL) {
211
214
  return options.baseURL;
212
215
  }
213
- const envName = options.domainEnv || DEFAULT_DOMAIN_ENV;
216
+ const envName = options.adminInnerApi ? options.adminDomainEnv || DEFAULT_ADMIN_DOMAIN_ENV : options.domainEnv || DEFAULT_DOMAIN_ENV;
214
217
  const domain = process.env[envName];
215
218
  if (!domain) {
216
219
  throw new Error(`\u5E73\u53F0\u6A21\u5F0F\u9700\u8981\u57FA\u7840\u57DF\u540D\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${envName}`);
@@ -233,6 +236,7 @@ function registerPlatformPlugin(interceptors, options) {
233
236
  expireTimeMs,
234
237
  refreshBeforeMs: options.refreshBeforeMs
235
238
  });
239
+ const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;
236
240
  interceptors.request.use((config) => {
237
241
  ensureNoAccessKeyOverride("request.platformAuth.customClaims", config.platformAuth?.customClaims);
238
242
  const claims = {
@@ -246,7 +250,12 @@ function registerPlatformPlugin(interceptors, options) {
246
250
  "Authorization": `Bearer ${token}`,
247
251
  "x-api-key": accessKey
248
252
  };
249
- console.log(111111111, headers);
253
+ if (options.adminInnerApi) {
254
+ const clientToken = process.env[clientTokenEnv];
255
+ if (clientToken) {
256
+ headers[MIAODA_CLIENT_TOKEN_HEADER] = clientToken;
257
+ }
258
+ }
250
259
  return {
251
260
  ...config,
252
261
  headers
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/helper/jwt.ts","../src/helper/jwt-manager.ts","../src/plugins/platform-plugin.ts","../src/core/InterceptorManager.ts","../src/core/utils.ts","../src/core/types.ts","../src/core/HttpClient.ts"],"sourcesContent":["export { HttpClient } from './core/HttpClient';\nexport { HttpError } from './core/types';\nexport type {\n RequestConfig,\n HttpClientConfig,\n RequestInterceptor,\n ResponseInterceptor,\n SecurityConfig,\n SafeRequestConfig,\n} from './core/types';\n\nexport type { CustomClaims, JWTClaims, JWTConfig, JWTVerifyOptions } from './helper/jwt';\nexport {\n generateJWTToken,\n parseJWTTokenWithVerify,\n DEFAULT_JWT_EXPIRE_TIME_MS,\n DEFAULT_CLOCK_TOLERANCE_SEC,\n} from './helper/jwt';\n\nexport type {\n PlatformPluginOptions,\n PlatformRequestAuthConfig,\n PlatformClaims,\n} from './plugins/platform-plugin';\nexport {\n registerPlatformPlugin,\n resolvePlatformBaseURL,\n} from './plugins/platform-plugin';\n\n","import { createHmac, randomUUID, timingSafeEqual } from 'crypto';\n\nexport interface CustomClaims {\n tenant_id?: number;\n user_id?: string;\n app_id?: string;\n app_env?: string;\n sandbox_id?: string;\n access_key: string;\n}\n\nexport interface JWTClaims extends CustomClaims {\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n jti: string;\n [key: string]: any;\n}\n\nexport interface JWTConfig {\n accessKey: string;\n secretKey: string;\n expireTimeMs: number;\n}\n\nexport interface JWTVerifyOptions {\n /**\n * 时钟容差(秒),用于处理服务器时间不同步的情况\n * 默认 60 秒\n */\n clockTolerance?: number;\n\n /**\n * 是否跳过过期验证(仅用于调试)\n * 默认 false\n */\n skipExpiration?: boolean;\n}\n\nexport const DEFAULT_JWT_EXPIRE_TIME_MS = 30 * 60 * 1000;\nexport const DEFAULT_CLOCK_TOLERANCE_SEC = 60;\n\nconst JWT_HEADER = {\n alg: 'HS256',\n typ: 'JWT',\n};\n\nexport function generateJWTToken(\n customClaims: CustomClaims,\n config: JWTConfig,\n): string {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const payload: JWTClaims = {\n ...customClaims,\n iss: customClaims.access_key,\n iat: nowSeconds,\n nbf: nowSeconds,\n exp: nowSeconds + Math.floor(config.expireTimeMs / 1000),\n jti: randomUUID(),\n };\n\n const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));\n const encodedPayload = base64UrlEncode(JSON.stringify(payload));\n const signature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n return `${encodedHeader}.${encodedPayload}.${signature}`;\n}\n\nexport function parseJWTTokenWithVerify(\n tokenString: string,\n config: JWTConfig,\n options?: JWTVerifyOptions,\n): JWTClaims {\n const segments = tokenString.split('.');\n if (segments.length !== 3) {\n throw new Error('invalid JWT token format');\n }\n\n const [encodedHeader, encodedPayload, signature] = segments;\n\n // 1. 验证 header\n const headerJson = JSON.parse(base64UrlDecode(encodedHeader));\n if (headerJson.alg !== 'HS256') {\n throw new Error('unsupported JWT alg');\n }\n\n // 2. 验证签名\n const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n const expectedBuffer = Buffer.from(expectedSignature);\n const signatureBuffer = Buffer.from(signature);\n if (\n expectedBuffer.length !== signatureBuffer.length ||\n !timingSafeEqual(expectedBuffer, signatureBuffer)\n ) {\n throw new Error('JWT signature verification failed');\n }\n\n // 3. 解析 payload\n const payload = JSON.parse(base64UrlDecode(encodedPayload)) as JWTClaims;\n\n // 4. 验证时间声明\n if (!options?.skipExpiration) {\n const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;\n const now = Math.floor(Date.now() / 1000);\n\n // 验证 exp (过期时间)\n if (payload.exp !== undefined) {\n if (payload.exp + clockTolerance < now) {\n throw new Error(\n `JWT token expired at ${new Date(payload.exp * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 nbf (生效时间)\n if (payload.nbf !== undefined) {\n if (payload.nbf - clockTolerance > now) {\n throw new Error(\n `JWT token not yet valid, will be valid at ${new Date(payload.nbf * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 iat (签发时间) - 防止未来时间的 token\n if (payload.iat !== undefined) {\n if (payload.iat - clockTolerance > now) {\n throw new Error(\n `JWT token issued in the future at ${new Date(payload.iat * 1000).toISOString()}`,\n );\n }\n }\n }\n\n return payload;\n}\n\nfunction sign(input: string, secret: string): string {\n return createHmac('sha256', secret)\n .update(input)\n .digest('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlEncode(input: string): string {\n return Buffer.from(input)\n .toString('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlDecode(input: string): string {\n const padLength = (4 - (input.length % 4 || 4)) % 4;\n const padded = `${input}${'='.repeat(padLength)}`\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n return Buffer.from(padded, 'base64').toString('utf8');\n}\n","import { CustomClaims, JWTConfig, generateJWTToken } from './jwt';\n\ninterface CachedToken {\n token: string;\n expiresAtMs: number;\n}\n\ninterface JWTManagerConfig extends JWTConfig {\n /**\n * 在过期前多久刷新 token(毫秒)\n * 默认 5 分钟\n */\n refreshBeforeMs?: number;\n}\n\n/**\n * JWT Token 管理器,负责 token 的生成、缓存和自动刷新\n *\n * 缓存策略:\n * - 基于 claims 的哈希值作为缓存 key(支持多租户+多用户)\n * - 在 token 过期前 refreshBeforeMs 时间内自动刷新\n *\n * @example\n * ```typescript\n * const manager = new JWTTokenManager({\n * accessKey: 'your-access-key',\n * secretKey: 'your-secret-key',\n * expireTimeMs: 30 * 60 * 1000,\n * refreshBeforeMs: 5 * 60 * 1000,\n * });\n *\n * // 获取 token(会自动缓存)\n * const token = manager.getToken({\n * access_key: 'your-access-key',\n * tenant_id: 1001,\n * user_id: 'user123',\n * });\n * ```\n */\nexport class JWTTokenManager {\n private cache = new Map<string, CachedToken>();\n private config: Required<JWTManagerConfig>;\n\n constructor(config: JWTManagerConfig) {\n this.config = {\n refreshBeforeMs: 5 * 60 * 1000, // 默认 5 分钟\n ...config,\n };\n }\n\n /**\n * 获取 JWT token,优先使用缓存\n *\n * 如果缓存的 token 即将过期(距离过期时间小于 refreshBeforeMs),\n * 则会生成新的 token 并更新缓存\n *\n * @param claims - JWT claims\n * @returns JWT token 字符串\n */\n getToken(claims: CustomClaims): string {\n const cacheKey = this.getCacheKey(claims);\n const cached = this.cache.get(cacheKey);\n const now = Date.now();\n\n // 检查缓存是否有效(未过期且不需要刷新)\n if (cached && cached.expiresAtMs - now > this.config.refreshBeforeMs) {\n return cached.token;\n }\n\n // 生成新 token\n const token = generateJWTToken(claims, this.config);\n const expiresAtMs = now + this.config.expireTimeMs;\n\n this.cache.set(cacheKey, { token, expiresAtMs });\n\n return token;\n }\n\n /**\n * 清除所有缓存的 token\n */\n clearCache(): void {\n this.cache.clear();\n }\n\n /**\n * 清除特定 claims 的缓存\n *\n * @param claims - 要清除的 claims\n */\n clearCacheFor(claims: CustomClaims): void {\n const cacheKey = this.getCacheKey(claims);\n this.cache.delete(cacheKey);\n }\n\n /**\n * 生成缓存 key(基于关键的 claims 字段)\n *\n * 使用稳定的字段顺序确保相同 claims 生成相同 key\n *\n * @private\n */\n private getCacheKey(claims: CustomClaims): string {\n // 使用稳定的字段顺序确保相同 claims 生成相同 key\n const parts = [\n claims.access_key,\n claims.tenant_id?.toString() || '',\n claims.user_id || '',\n claims.app_id || '',\n claims.app_env || '',\n claims.sandbox_id || '',\n ];\n return parts.join(':');\n }\n\n /**\n * 获取缓存统计信息(用于监控和调试)\n *\n * @returns 缓存统计信息\n */\n getCacheStats() {\n const now = Date.now();\n let validCount = 0;\n let expiredCount = 0;\n\n for (const cached of this.cache.values()) {\n if (cached.expiresAtMs > now) {\n validCount++;\n } else {\n expiredCount++;\n }\n }\n\n return {\n total: this.cache.size,\n valid: validCount,\n expired: expiredCount,\n };\n }\n}\n","import type { RequestConfig, RequestInterceptor } from '../core/types';\nimport {\n DEFAULT_JWT_EXPIRE_TIME_MS,\n CustomClaims,\n} from '../helper/jwt';\nimport { JWTTokenManager } from '../helper/jwt-manager';\n\nexport type PlatformClaims = Omit<CustomClaims, 'access_key'>;\n\nexport interface PlatformPluginOptions {\n enabled?: boolean;\n baseURL?: string;\n domainEnv?: string;\n accessKeyEnv?: string;\n secretKeyEnv?: string;\n accessKey?: string;\n secretKey?: string;\n expireTimeMs?: number;\n refreshBeforeMs?: number; // 新增:在过期前多久刷新 token\n defaultClaims?: PlatformClaims;\n}\n\nexport interface PlatformRequestAuthConfig {\n customClaims?: PlatformClaims;\n}\n\nconst DEFAULT_DOMAIN_ENV = 'FORCE_AUTHN_INNERAPI_DOMAIN';\nconst DEFAULT_ACCESS_KEY_ENV = 'FORCE_AUTHN_ACCESS_KEY';\nconst DEFAULT_SECRET_KEY_ENV = 'FORCE_AUTHN_ACCESS_SECRET';\n\nexport function resolvePlatformBaseURL(options?: PlatformPluginOptions): string | undefined {\n if (!options?.enabled) {\n return options?.baseURL;\n }\n\n if (options.baseURL) {\n return options.baseURL;\n }\n\n const envName = options.domainEnv || DEFAULT_DOMAIN_ENV;\n const domain = process.env[envName];\n if (!domain) {\n throw new Error(`平台模式需要基础域名,请设置环境变量 ${envName}`);\n }\n return domain;\n}\n\ntype RequestInterceptorManager = {\n use: (\n onFulfilled?: RequestInterceptor['onFulfilled'],\n onRejected?: RequestInterceptor['onRejected'],\n ) => number;\n eject: (id: number) => void;\n};\n\ninterface InterceptorHost {\n request: RequestInterceptorManager;\n}\n\nexport function registerPlatformPlugin(\n interceptors: InterceptorHost,\n options: PlatformPluginOptions,\n): JWTTokenManager | undefined {\n if (!options.enabled) {\n return undefined;\n }\n\n ensureNoAccessKeyOverride('platform.defaultClaims', options.defaultClaims);\n\n // 提前检查 AK/SK,避免每次请求时检查\n const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;\n const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;\n\n const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || '');\n // if (!accessKey) {\n // throw new Error(`平台鉴权需要 access key,请设置环境变量 ${accessKeyEnv}`);\n // }\n\n const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n // if (!secretKey) {\n // throw new Error(`平台鉴权需要 secret key,请设置环境变量 ${secretKeyEnv}`);\n // }\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // 创建 JWT Token Manager(支持缓存和自动刷新)\n const tokenManager = new JWTTokenManager({\n accessKey,\n secretKey,\n expireTimeMs,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n\n // 注册请求拦截器\n interceptors.request.use((config: RequestConfig) => {\n ensureNoAccessKeyOverride('request.platformAuth.customClaims', config.platformAuth?.customClaims);\n\n const claims: CustomClaims = {\n ...(options.defaultClaims || {}),\n ...(config.platformAuth?.customClaims || {}),\n access_key: accessKey,\n };\n\n // 使用 token manager 获取缓存的 token\n const token = tokenManager.getToken(claims);\n\n // 统一使用 Record<string, string> 类型,由 client.ts 的 normalizeHeaders 处理\n const headers = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${token}`,\n 'x-api-key': accessKey,\n };\n\n console.log(111111111, headers)\n\n return {\n ...config,\n headers,\n };\n });\n\n // 返回 token manager 供外部使用(如手动清理缓存)\n return tokenManager;\n}\n\nfunction ensureNoAccessKeyOverride(name: string, claims?: PlatformClaims) {\n if (claims && Object.prototype.hasOwnProperty.call(claims, 'access_key')) {\n throw new Error(`${name} 不允许设置 access_key`);\n }\n}\n","/**\n * 拦截器管理器(模仿 Axios 的 InterceptorManager)\n */\nexport class InterceptorManager<T> {\n private interceptors: (T | null)[] = [];\n\n /**\n * 添加拦截器\n * @returns 拦截器 ID,用于后续移除\n */\n use(onFulfilled?: any, onRejected?: any): number {\n this.interceptors.push({ onFulfilled, onRejected } as T);\n return this.interceptors.length - 1;\n }\n\n /**\n * 移除拦截器\n */\n eject(id: number): void {\n if (this.interceptors[id]) {\n this.interceptors[id] = null;\n }\n }\n\n /**\n * 清空所有拦截器\n */\n clear(): void {\n this.interceptors = [];\n }\n\n /**\n * 遍历所有拦截器\n */\n forEach(fn: (interceptor: T) => void): void {\n this.interceptors.forEach((interceptor) => {\n if (interceptor !== null) {\n fn(interceptor);\n }\n });\n }\n}\n","/**\n * Checks if a value is a plain object.\n * @param value The value to check.\n * @returns True if the value is a plain object, false otherwise.\n */\nexport function isPlainObject(value: any): boolean {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nexport function normalizeHeaders(headers?: RequestInit['headers']): Record<string, string> {\n if (!headers) {\n return {};\n }\n\n if (headers instanceof Headers) {\n const normalized: Record<string, string> = {};\n headers.forEach((value, key) => {\n normalized[key] = value;\n });\n return normalized;\n }\n\n if (Array.isArray(headers)) {\n return headers.reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = value;\n return acc;\n }, {});\n }\n\n const normalized: Record<string, string> = {};\n Object.entries(headers as Record<string, string | readonly string[]>).forEach(\n ([key, value]) => {\n const normalizedValue = Array.isArray(value) ? value.join(',') : value;\n normalized[key] = normalizedValue as string;\n },\n );\n return normalized;\n}\n","import type { PlatformPluginOptions, PlatformRequestAuthConfig } from '../plugins/platform-plugin';\nimport { normalizeHeaders } from './utils';\n\n/**\n * 清理后的请求配置(移除敏感信息)\n */\nexport interface SafeRequestConfig extends Omit<RequestConfig, 'headers'> {\n headers?: Record<string, string>; // 已清理的 headers\n}\n\n/**\n * HTTP 错误对象\n *\n * 注意:为了安全考虑,error.config 中的敏感 headers (如 Authorization, x-api-key)\n * 会被替换为 '[REDACTED]'\n */\nexport class HttpError extends Error {\n public readonly isHttpError: boolean = true;\n public response: Response | undefined;\n public config: SafeRequestConfig;\n\n constructor(\n response: Response | undefined,\n config: RequestConfig,\n message?: string,\n ) {\n super(message || `Request failed with status ${response?.status || 'unknown'}`);\n this.name = 'HttpError';\n this.response = response;\n this.config = sanitizeConfig(config);\n Object.setPrototypeOf(this, HttpError.prototype);\n }\n}\n\nexport interface RequestConfig extends RequestInit {\n url?: string;\n params?: Record<string, any>;\n timeout?: number;\n baseURL?: string;\n platformAuth?: PlatformRequestAuthConfig;\n}\n\nexport interface SecurityConfig {\n /**\n * 允许的 URL 协议白名单\n * 默认: null (不限制)\n * 设置为 ['http:', 'https:'] 可限制只允许 HTTP/HTTPS\n */\n allowedProtocols?: string[] | null;\n\n /**\n * 最大响应体大小(字节)\n * 默认: 0 (不限制)\n * 设置为正数可限制响应体大小,防止内存耗尽攻击\n */\n maxResponseSize?: number;\n\n /**\n * 是否启用严格模式(启用所有安全检查)\n * 默认: false\n * 启用后会自动设置:\n * - allowedProtocols = ['http:', 'https:']\n * - maxResponseSize = 50MB\n */\n strictMode?: boolean;\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n}\n\n/**\n * 请求拦截器函数\n */\nexport type RequestInterceptor = {\n onFulfilled?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 响应拦截器函数(fetch 风格)\n */\nexport type ResponseInterceptor = {\n onFulfilled?: (response: Response) => Response | Promise<Response>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 清理配置中的敏感信息\n */\nfunction sanitizeConfig(config: RequestConfig): SafeRequestConfig {\n const { headers, ...rest } = config;\n\n return {\n ...rest,\n headers: sanitizeHeaders(headers),\n };\n}\n\n/**\n * 清理敏感 headers\n *\n * 将 Authorization, x-api-key, cookie 等敏感字段替换为 '[REDACTED]'\n */\nfunction sanitizeHeaders(headers?: RequestInit['headers']): Record<string, string> | undefined {\n if (!headers) {\n return undefined;\n }\n\n const normalized = normalizeHeaders(headers);\n const sanitized: Record<string, string> = {};\n\n const sensitiveHeaders = ['authorization', 'x-api-key', 'cookie', 'x-secret'];\n\n for (const [key, value] of Object.entries(normalized)) {\n const lowerKey = key.toLowerCase();\n if (sensitiveHeaders.includes(lowerKey)) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = value;\n }\n }\n\n return sanitized;\n}\n","import { registerPlatformPlugin, resolvePlatformBaseURL } from '../plugins/platform-plugin';\nimport { InterceptorManager } from './InterceptorManager';\nimport {\n HttpError,\n type HttpClientConfig,\n type RequestConfig,\n type RequestInterceptor,\n type ResponseInterceptor,\n type SecurityConfig,\n} from './types';\nimport { isPlainObject, normalizeHeaders } from './utils';\n\n/**\n * 技术栈无关的 HTTP 客户端\n *\n * 设计理念:\n * - 基于原生 fetch API,零依赖\n * - 拦截器模式遵循 Axios 标准,AI 模型和开发者都熟悉\n * - 可在任何 Node.js 环境使用(NestJS, Express, Koa, 纯 Node)\n *\n * @example\n * ```typescript\n * const client = new HttpClient({\n * baseURL: 'https://api.example.com',\n * timeout: 5000,\n * });\n *\n * // 添加请求拦截器(类似 Axios)\n * client.interceptors.request.use(\n * config => {\n * config.headers = config.headers || {};\n * config.headers['Authorization'] = 'Bearer token';\n * return config;\n * },\n * error => Promise.reject(error)\n * );\n *\n * // 添加响应拦截器(类似 Axios)\n * client.interceptors.response.use(\n * response => {\n * console.log('Response:', response.status);\n * return response;\n * },\n * error => {\n * if (error.response?.status === 401) {\n * // 处理未授权\n * }\n * return Promise.reject(error);\n * }\n * );\n *\n * // 发起请求\n * const response = await client.get('/users');\n * console.log(response.data);\n * ```\n */\nexport class HttpClient {\n private defaultConfig: RequestConfig;\n private securityConfig: Required<SecurityConfig>;\n /**\n * 拦截器管理器(API 与 Axios 一致)\n */\n public interceptors = {\n request: new InterceptorManager<RequestInterceptor>(),\n response: new InterceptorManager<ResponseInterceptor>(),\n };\n\n constructor(config?: HttpClientConfig) {\n const { platform, security, ...restConfig } = config || {};\n\n this.defaultConfig = {\n timeout: 5000,\n ...restConfig,\n };\n\n // 初始化安全配置\n const strictMode = security?.strictMode ?? false;\n this.securityConfig = {\n allowedProtocols: strictMode ? ['http:', 'https:'] : null,\n maxResponseSize: strictMode ? 50 * 1024 * 1024 : 0, // 50MB\n strictMode,\n ...security,\n };\n\n if (platform?.enabled) {\n if (!this.defaultConfig.baseURL) {\n this.defaultConfig.baseURL = resolvePlatformBaseURL(platform);\n }\n registerPlatformPlugin(this.interceptors, platform);\n }\n }\n\n /**\n * GET 请求\n */\n async get(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'GET' });\n }\n\n /**\n * POST 请求\n */\n async post(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'POST',\n body,\n headers,\n });\n }\n\n /**\n * PUT 请求\n */\n async put(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PUT',\n body,\n headers,\n });\n }\n\n /**\n * DELETE 请求\n */\n async delete(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'DELETE' });\n }\n\n /**\n * PATCH 请求\n */\n async patch(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PATCH',\n body,\n headers,\n });\n }\n\n /**\n * 核心请求方法(fetch 风格)\n */\n async request(config: RequestConfig): Promise<Response> {\n // 1. 合并配置\n // Perform a case-insensitive header merge that preserves the final key's casing.\n const baseHeaders = normalizeHeaders(this.defaultConfig.headers);\n const overrideHeaders = normalizeHeaders(config.headers);\n const mergedKeyMap: Record<string, { key: string; value: string }> = {};\n\n for (const key in baseHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: baseHeaders[key] };\n }\n for (const key in overrideHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: overrideHeaders[key] };\n }\n\n const finalHeaders: Record<string, string> = {};\n for (const lowerKey in mergedKeyMap) {\n const { key, value } = mergedKeyMap[lowerKey];\n finalHeaders[key] = value;\n }\n\n let mergedConfig: RequestConfig = {\n ...this.defaultConfig,\n ...config,\n headers: finalHeaders,\n };\n\n // 2. 执行请求拦截器\n try {\n mergedConfig = await this.runRequestInterceptors(mergedConfig);\n } catch (error) {\n return Promise.reject(error);\n }\n\n // 3. 统一 normalize headers(拦截器可能返回 Headers 对象)\n mergedConfig.headers = normalizeHeaders(mergedConfig.headers);\n\n // 4. 构建完整 URL\n const fullUrl = this.buildUrl(mergedConfig.url!, mergedConfig.params);\n\n // 5. 设置超时和取消\n const timeout = mergedConfig.timeout || this.defaultConfig.timeout || 5000;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n // 监听外部 signal\n const externalSignal = mergedConfig.signal;\n if (externalSignal) {\n // 如果外部 signal 已经终止,立即终止\n if (externalSignal.aborted) {\n clearTimeout(timeoutId);\n controller.abort();\n } else {\n // 使用 { once: true } 避免内存泄漏\n externalSignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n }\n\n // 6. 准备 fetch 配置(排除自定义字段)\n const { platformAuth, params, timeout: _timeout, baseURL, url, ...fetchOptions } = mergedConfig;\n\n try {\n // 7. 发起请求\n const response = await fetch(fullUrl, {\n ...fetchOptions,\n signal: controller.signal, // 传递我们自己的 controller signal\n });\n\n clearTimeout(timeoutId);\n\n // 安全检查:响应体大小限制\n if (this.securityConfig.maxResponseSize > 0) {\n const contentLength = response.headers.get('content-length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > this.securityConfig.maxResponseSize) {\n throw new Error(\n `Response size ${size} bytes exceeds limit of ${this.securityConfig.maxResponseSize} bytes`\n );\n }\n }\n }\n\n // 检查响应状态\n if (!response.ok) {\n const error = new HttpError(response, mergedConfig);\n return this.runResponseInterceptors(Promise.reject(error));\n }\n\n // 执行响应拦截器(直接返回 Response)\n return this.runResponseInterceptors(Promise.resolve(response));\n } catch (error) {\n clearTimeout(timeoutId);\n\n // 网络错误或超时错误\n const httpError =\n error instanceof HttpError\n ? error\n : new HttpError(\n undefined,\n mergedConfig,\n (error as Error).name === 'AbortError'\n ? 'Request aborted'\n : (error as Error).message,\n );\n\n return this.runResponseInterceptors(Promise.reject(httpError));\n }\n }\n\n /**\n * 执行请求拦截器链\n */\n private runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {\n let promise: Promise<RequestConfig> = Promise.resolve(config);\n\n const interceptors: RequestInterceptor[] = [];\n this.interceptors.request.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n promise = promise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return promise;\n }\n\n /**\n * 执行响应拦截器链\n */\n private async runResponseInterceptors(\n promise: Promise<Response>,\n ): Promise<Response> {\n let currentPromise = promise;\n\n // 按顺序执行所有响应拦截器\n const interceptors: ResponseInterceptor[] = [];\n this.interceptors.response.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n currentPromise = currentPromise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return currentPromise;\n }\n\n /**\n * 构建完整 URL(包含 baseURL 和 query params)\n */\n private buildUrl(url: string, params?: Record<string, any>): string {\n const baseURL = this.defaultConfig.baseURL;\n\n // 拼接 baseURL 和 url(Axios 风格)\n // - 如果 url 是绝对路径(含协议),直接使用 url\n // - 否则拼接 baseURL 和 url,正确处理斜杠\n let combinedUrl: string;\n if (!baseURL || /^https?:\\/\\//i.test(url)) {\n combinedUrl = url;\n } else {\n combinedUrl = baseURL.replace(/\\/+$/, '') + '/' + url.replace(/^\\/+/, '');\n }\n\n const fullUrl = new URL(combinedUrl);\n\n // 协议白名单检查\n const { allowedProtocols } = this.securityConfig;\n if (allowedProtocols && !allowedProtocols.includes(fullUrl.protocol)) {\n throw new Error(\n `Protocol ${fullUrl.protocol} is not allowed. Allowed: ${allowedProtocols.join(', ')}`\n );\n }\n\n // 合并 query params\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n fullUrl.searchParams.set(key, String(value));\n }\n }\n\n return fullUrl.href;\n }\n}\n"],"mappings":"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","names":["normalized"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/helper/jwt.ts","../src/helper/jwt-manager.ts","../src/plugins/platform-plugin.ts","../src/core/InterceptorManager.ts","../src/core/utils.ts","../src/core/types.ts","../src/core/HttpClient.ts"],"sourcesContent":["export { HttpClient } from './core/HttpClient';\nexport { HttpError } from './core/types';\nexport type {\n RequestConfig,\n HttpClientConfig,\n RequestInterceptor,\n ResponseInterceptor,\n SecurityConfig,\n SafeRequestConfig,\n} from './core/types';\n\nexport type { CustomClaims, JWTClaims, JWTConfig, JWTVerifyOptions } from './helper/jwt';\nexport {\n generateJWTToken,\n parseJWTTokenWithVerify,\n DEFAULT_JWT_EXPIRE_TIME_MS,\n DEFAULT_CLOCK_TOLERANCE_SEC,\n} from './helper/jwt';\n\nexport type {\n PlatformPluginOptions,\n PlatformRequestAuthConfig,\n PlatformClaims,\n} from './plugins/platform-plugin';\nexport {\n registerPlatformPlugin,\n resolvePlatformBaseURL,\n} from './plugins/platform-plugin';\n\n","import { createHmac, randomUUID, timingSafeEqual } from 'crypto';\n\nexport interface CustomClaims {\n tenant_id?: number;\n user_id?: string;\n app_id?: string;\n app_env?: string;\n sandbox_id?: string;\n access_key: string;\n}\n\nexport interface JWTClaims extends CustomClaims {\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n jti: string;\n [key: string]: any;\n}\n\nexport interface JWTConfig {\n accessKey: string;\n secretKey: string;\n expireTimeMs: number;\n}\n\nexport interface JWTVerifyOptions {\n /**\n * 时钟容差(秒),用于处理服务器时间不同步的情况\n * 默认 60 秒\n */\n clockTolerance?: number;\n\n /**\n * 是否跳过过期验证(仅用于调试)\n * 默认 false\n */\n skipExpiration?: boolean;\n}\n\nexport const DEFAULT_JWT_EXPIRE_TIME_MS = 30 * 60 * 1000;\nexport const DEFAULT_CLOCK_TOLERANCE_SEC = 60;\n\nconst JWT_HEADER = {\n alg: 'HS256',\n typ: 'JWT',\n};\n\nexport function generateJWTToken(\n customClaims: CustomClaims,\n config: JWTConfig,\n): string {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const payload: JWTClaims = {\n ...customClaims,\n iss: customClaims.access_key,\n iat: nowSeconds,\n nbf: nowSeconds,\n exp: nowSeconds + Math.floor(config.expireTimeMs / 1000),\n jti: randomUUID(),\n };\n\n const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));\n const encodedPayload = base64UrlEncode(JSON.stringify(payload));\n const signature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n return `${encodedHeader}.${encodedPayload}.${signature}`;\n}\n\nexport function parseJWTTokenWithVerify(\n tokenString: string,\n config: JWTConfig,\n options?: JWTVerifyOptions,\n): JWTClaims {\n const segments = tokenString.split('.');\n if (segments.length !== 3) {\n throw new Error('invalid JWT token format');\n }\n\n const [encodedHeader, encodedPayload, signature] = segments;\n\n // 1. 验证 header\n const headerJson = JSON.parse(base64UrlDecode(encodedHeader));\n if (headerJson.alg !== 'HS256') {\n throw new Error('unsupported JWT alg');\n }\n\n // 2. 验证签名\n const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n const expectedBuffer = Buffer.from(expectedSignature);\n const signatureBuffer = Buffer.from(signature);\n if (\n expectedBuffer.length !== signatureBuffer.length ||\n !timingSafeEqual(expectedBuffer, signatureBuffer)\n ) {\n throw new Error('JWT signature verification failed');\n }\n\n // 3. 解析 payload\n const payload = JSON.parse(base64UrlDecode(encodedPayload)) as JWTClaims;\n\n // 4. 验证时间声明\n if (!options?.skipExpiration) {\n const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;\n const now = Math.floor(Date.now() / 1000);\n\n // 验证 exp (过期时间)\n if (payload.exp !== undefined) {\n if (payload.exp + clockTolerance < now) {\n throw new Error(\n `JWT token expired at ${new Date(payload.exp * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 nbf (生效时间)\n if (payload.nbf !== undefined) {\n if (payload.nbf - clockTolerance > now) {\n throw new Error(\n `JWT token not yet valid, will be valid at ${new Date(payload.nbf * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 iat (签发时间) - 防止未来时间的 token\n if (payload.iat !== undefined) {\n if (payload.iat - clockTolerance > now) {\n throw new Error(\n `JWT token issued in the future at ${new Date(payload.iat * 1000).toISOString()}`,\n );\n }\n }\n }\n\n return payload;\n}\n\nfunction sign(input: string, secret: string): string {\n return createHmac('sha256', secret)\n .update(input)\n .digest('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlEncode(input: string): string {\n return Buffer.from(input)\n .toString('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlDecode(input: string): string {\n const padLength = (4 - (input.length % 4 || 4)) % 4;\n const padded = `${input}${'='.repeat(padLength)}`\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n return Buffer.from(padded, 'base64').toString('utf8');\n}\n","import { CustomClaims, JWTConfig, generateJWTToken } from './jwt';\n\ninterface CachedToken {\n token: string;\n expiresAtMs: number;\n}\n\ninterface JWTManagerConfig extends JWTConfig {\n /**\n * 在过期前多久刷新 token(毫秒)\n * 默认 5 分钟\n */\n refreshBeforeMs?: number;\n}\n\n/**\n * JWT Token 管理器,负责 token 的生成、缓存和自动刷新\n *\n * 缓存策略:\n * - 基于 claims 的哈希值作为缓存 key(支持多租户+多用户)\n * - 在 token 过期前 refreshBeforeMs 时间内自动刷新\n *\n * @example\n * ```typescript\n * const manager = new JWTTokenManager({\n * accessKey: 'your-access-key',\n * secretKey: 'your-secret-key',\n * expireTimeMs: 30 * 60 * 1000,\n * refreshBeforeMs: 5 * 60 * 1000,\n * });\n *\n * // 获取 token(会自动缓存)\n * const token = manager.getToken({\n * access_key: 'your-access-key',\n * tenant_id: 1001,\n * user_id: 'user123',\n * });\n * ```\n */\nexport class JWTTokenManager {\n private cache = new Map<string, CachedToken>();\n private config: Required<JWTManagerConfig>;\n\n constructor(config: JWTManagerConfig) {\n this.config = {\n refreshBeforeMs: 5 * 60 * 1000, // 默认 5 分钟\n ...config,\n };\n }\n\n /**\n * 获取 JWT token,优先使用缓存\n *\n * 如果缓存的 token 即将过期(距离过期时间小于 refreshBeforeMs),\n * 则会生成新的 token 并更新缓存\n *\n * @param claims - JWT claims\n * @returns JWT token 字符串\n */\n getToken(claims: CustomClaims): string {\n const cacheKey = this.getCacheKey(claims);\n const cached = this.cache.get(cacheKey);\n const now = Date.now();\n\n // 检查缓存是否有效(未过期且不需要刷新)\n if (cached && cached.expiresAtMs - now > this.config.refreshBeforeMs) {\n return cached.token;\n }\n\n // 生成新 token\n const token = generateJWTToken(claims, this.config);\n const expiresAtMs = now + this.config.expireTimeMs;\n\n this.cache.set(cacheKey, { token, expiresAtMs });\n\n return token;\n }\n\n /**\n * 清除所有缓存的 token\n */\n clearCache(): void {\n this.cache.clear();\n }\n\n /**\n * 清除特定 claims 的缓存\n *\n * @param claims - 要清除的 claims\n */\n clearCacheFor(claims: CustomClaims): void {\n const cacheKey = this.getCacheKey(claims);\n this.cache.delete(cacheKey);\n }\n\n /**\n * 生成缓存 key(基于关键的 claims 字段)\n *\n * 使用稳定的字段顺序确保相同 claims 生成相同 key\n *\n * @private\n */\n private getCacheKey(claims: CustomClaims): string {\n // 使用稳定的字段顺序确保相同 claims 生成相同 key\n const parts = [\n claims.access_key,\n claims.tenant_id?.toString() || '',\n claims.user_id || '',\n claims.app_id || '',\n claims.app_env || '',\n claims.sandbox_id || '',\n ];\n return parts.join(':');\n }\n\n /**\n * 获取缓存统计信息(用于监控和调试)\n *\n * @returns 缓存统计信息\n */\n getCacheStats() {\n const now = Date.now();\n let validCount = 0;\n let expiredCount = 0;\n\n for (const cached of this.cache.values()) {\n if (cached.expiresAtMs > now) {\n validCount++;\n } else {\n expiredCount++;\n }\n }\n\n return {\n total: this.cache.size,\n valid: validCount,\n expired: expiredCount,\n };\n }\n}\n","import type { RequestConfig, RequestInterceptor } from '../core/types';\nimport {\n DEFAULT_JWT_EXPIRE_TIME_MS,\n CustomClaims,\n} from '../helper/jwt';\nimport { JWTTokenManager } from '../helper/jwt-manager';\n\nexport type PlatformClaims = Omit<CustomClaims, 'access_key'>;\n\nexport interface PlatformPluginOptions {\n enabled?: boolean;\n baseURL?: string;\n domainEnv?: string;\n accessKeyEnv?: string;\n secretKeyEnv?: string;\n accessKey?: string;\n secretKey?: string;\n expireTimeMs?: number;\n refreshBeforeMs?: number; // 新增:在过期前多久刷新 token\n defaultClaims?: PlatformClaims;\n /**\n * 管理端 innerapi 链路(仅用于妙搭开发态)\n * 启用后:\n * - baseURL 改从 adminDomainEnv(默认 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX)读取\n * - 每次请求从 clientTokenEnv(默认 MIAODA_AUTHN_CODE)读取用户凭证,\n * 注入为 X-Miaoda-Client-Token 请求头(读不到则不注入)\n * - AK/SK 的 Authorization + x-api-key 照旧\n */\n adminInnerApi?: boolean;\n adminDomainEnv?: string;\n clientTokenEnv?: string;\n}\n\nexport interface PlatformRequestAuthConfig {\n customClaims?: PlatformClaims;\n}\n\nconst DEFAULT_DOMAIN_ENV = 'FORCE_AUTHN_INNERAPI_DOMAIN';\nconst DEFAULT_ACCESS_KEY_ENV = 'FORCE_AUTHN_ACCESS_KEY';\nconst DEFAULT_SECRET_KEY_ENV = 'FORCE_AUTHN_ACCESS_SECRET';\nconst DEFAULT_ADMIN_DOMAIN_ENV = 'MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX';\nconst DEFAULT_CLIENT_TOKEN_ENV = 'MIAODA_AUTHN_CODE';\nconst MIAODA_CLIENT_TOKEN_HEADER = 'X-Miaoda-Client-Token';\n\nexport function resolvePlatformBaseURL(options?: PlatformPluginOptions): string | undefined {\n if (!options?.enabled) {\n return options?.baseURL;\n }\n\n if (options.baseURL) {\n return options.baseURL;\n }\n\n const envName = options.adminInnerApi\n ? (options.adminDomainEnv || DEFAULT_ADMIN_DOMAIN_ENV)\n : (options.domainEnv || DEFAULT_DOMAIN_ENV);\n const domain = process.env[envName];\n if (!domain) {\n throw new Error(`平台模式需要基础域名,请设置环境变量 ${envName}`);\n }\n return domain;\n}\n\ntype RequestInterceptorManager = {\n use: (\n onFulfilled?: RequestInterceptor['onFulfilled'],\n onRejected?: RequestInterceptor['onRejected'],\n ) => number;\n eject: (id: number) => void;\n};\n\ninterface InterceptorHost {\n request: RequestInterceptorManager;\n}\n\nexport function registerPlatformPlugin(\n interceptors: InterceptorHost,\n options: PlatformPluginOptions,\n): JWTTokenManager | undefined {\n if (!options.enabled) {\n return undefined;\n }\n\n ensureNoAccessKeyOverride('platform.defaultClaims', options.defaultClaims);\n\n // 提前检查 AK/SK,避免每次请求时检查\n const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;\n const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;\n\n const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || '');\n // if (!accessKey) {\n // throw new Error(`平台鉴权需要 access key,请设置环境变量 ${accessKeyEnv}`);\n // }\n\n const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n // if (!secretKey) {\n // throw new Error(`平台鉴权需要 secret key,请设置环境变量 ${secretKeyEnv}`);\n // }\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // 创建 JWT Token Manager(支持缓存和自动刷新)\n const tokenManager = new JWTTokenManager({\n accessKey,\n secretKey,\n expireTimeMs,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n\n const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;\n\n // 注册请求拦截器\n interceptors.request.use((config: RequestConfig) => {\n ensureNoAccessKeyOverride('request.platformAuth.customClaims', config.platformAuth?.customClaims);\n\n const claims: CustomClaims = {\n ...(options.defaultClaims || {}),\n ...(config.platformAuth?.customClaims || {}),\n access_key: accessKey,\n };\n\n // 使用 token manager 获取缓存的 token\n const token = tokenManager.getToken(claims);\n\n // 统一使用 Record<string, string> 类型,由 client.ts 的 normalizeHeaders 处理\n const headers: Record<string, string> = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${token}`,\n 'x-api-key': accessKey,\n };\n\n // 管理端 innerapi:运行时读取用户凭证并注入\n if (options.adminInnerApi) {\n const clientToken = process.env[clientTokenEnv];\n if (clientToken) {\n headers[MIAODA_CLIENT_TOKEN_HEADER] = clientToken;\n }\n }\n\n return {\n ...config,\n headers,\n };\n });\n\n // 返回 token manager 供外部使用(如手动清理缓存)\n return tokenManager;\n}\n\nfunction ensureNoAccessKeyOverride(name: string, claims?: PlatformClaims) {\n if (claims && Object.prototype.hasOwnProperty.call(claims, 'access_key')) {\n throw new Error(`${name} 不允许设置 access_key`);\n }\n}\n","/**\n * 拦截器管理器(模仿 Axios 的 InterceptorManager)\n */\nexport class InterceptorManager<T> {\n private interceptors: (T | null)[] = [];\n\n /**\n * 添加拦截器\n * @returns 拦截器 ID,用于后续移除\n */\n use(onFulfilled?: any, onRejected?: any): number {\n this.interceptors.push({ onFulfilled, onRejected } as T);\n return this.interceptors.length - 1;\n }\n\n /**\n * 移除拦截器\n */\n eject(id: number): void {\n if (this.interceptors[id]) {\n this.interceptors[id] = null;\n }\n }\n\n /**\n * 清空所有拦截器\n */\n clear(): void {\n this.interceptors = [];\n }\n\n /**\n * 遍历所有拦截器\n */\n forEach(fn: (interceptor: T) => void): void {\n this.interceptors.forEach((interceptor) => {\n if (interceptor !== null) {\n fn(interceptor);\n }\n });\n }\n}\n","/**\n * Checks if a value is a plain object.\n * @param value The value to check.\n * @returns True if the value is a plain object, false otherwise.\n */\nexport function isPlainObject(value: any): boolean {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nexport function normalizeHeaders(headers?: RequestInit['headers']): Record<string, string> {\n if (!headers) {\n return {};\n }\n\n if (headers instanceof Headers) {\n const normalized: Record<string, string> = {};\n headers.forEach((value, key) => {\n normalized[key] = value;\n });\n return normalized;\n }\n\n if (Array.isArray(headers)) {\n return headers.reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = value;\n return acc;\n }, {});\n }\n\n const normalized: Record<string, string> = {};\n Object.entries(headers as Record<string, string | readonly string[]>).forEach(\n ([key, value]) => {\n const normalizedValue = Array.isArray(value) ? value.join(',') : value;\n normalized[key] = normalizedValue as string;\n },\n );\n return normalized;\n}\n","import type { PlatformPluginOptions, PlatformRequestAuthConfig } from '../plugins/platform-plugin';\nimport { normalizeHeaders } from './utils';\n\n/**\n * 清理后的请求配置(移除敏感信息)\n */\nexport interface SafeRequestConfig extends Omit<RequestConfig, 'headers'> {\n headers?: Record<string, string>; // 已清理的 headers\n}\n\n/**\n * HTTP 错误对象\n *\n * 注意:为了安全考虑,error.config 中的敏感 headers (如 Authorization, x-api-key)\n * 会被替换为 '[REDACTED]'\n */\nexport class HttpError extends Error {\n public readonly isHttpError: boolean = true;\n public response: Response | undefined;\n public config: SafeRequestConfig;\n\n constructor(\n response: Response | undefined,\n config: RequestConfig,\n message?: string,\n ) {\n super(message || `Request failed with status ${response?.status || 'unknown'}`);\n this.name = 'HttpError';\n this.response = response;\n this.config = sanitizeConfig(config);\n Object.setPrototypeOf(this, HttpError.prototype);\n }\n}\n\nexport interface RequestConfig extends RequestInit {\n url?: string;\n params?: Record<string, any>;\n timeout?: number;\n baseURL?: string;\n platformAuth?: PlatformRequestAuthConfig;\n}\n\nexport interface SecurityConfig {\n /**\n * 允许的 URL 协议白名单\n * 默认: null (不限制)\n * 设置为 ['http:', 'https:'] 可限制只允许 HTTP/HTTPS\n */\n allowedProtocols?: string[] | null;\n\n /**\n * 最大响应体大小(字节)\n * 默认: 0 (不限制)\n * 设置为正数可限制响应体大小,防止内存耗尽攻击\n */\n maxResponseSize?: number;\n\n /**\n * 是否启用严格模式(启用所有安全检查)\n * 默认: false\n * 启用后会自动设置:\n * - allowedProtocols = ['http:', 'https:']\n * - maxResponseSize = 50MB\n */\n strictMode?: boolean;\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n}\n\n/**\n * 请求拦截器函数\n */\nexport type RequestInterceptor = {\n onFulfilled?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 响应拦截器函数(fetch 风格)\n */\nexport type ResponseInterceptor = {\n onFulfilled?: (response: Response) => Response | Promise<Response>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 清理配置中的敏感信息\n */\nfunction sanitizeConfig(config: RequestConfig): SafeRequestConfig {\n const { headers, ...rest } = config;\n\n return {\n ...rest,\n headers: sanitizeHeaders(headers),\n };\n}\n\n/**\n * 清理敏感 headers\n *\n * 将 Authorization, x-api-key, cookie 等敏感字段替换为 '[REDACTED]'\n */\nfunction sanitizeHeaders(headers?: RequestInit['headers']): Record<string, string> | undefined {\n if (!headers) {\n return undefined;\n }\n\n const normalized = normalizeHeaders(headers);\n const sanitized: Record<string, string> = {};\n\n const sensitiveHeaders = ['authorization', 'x-api-key', 'cookie', 'x-secret'];\n\n for (const [key, value] of Object.entries(normalized)) {\n const lowerKey = key.toLowerCase();\n if (sensitiveHeaders.includes(lowerKey)) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = value;\n }\n }\n\n return sanitized;\n}\n","import { registerPlatformPlugin, resolvePlatformBaseURL } from '../plugins/platform-plugin';\nimport { InterceptorManager } from './InterceptorManager';\nimport {\n HttpError,\n type HttpClientConfig,\n type RequestConfig,\n type RequestInterceptor,\n type ResponseInterceptor,\n type SecurityConfig,\n} from './types';\nimport { isPlainObject, normalizeHeaders } from './utils';\n\n/**\n * 技术栈无关的 HTTP 客户端\n *\n * 设计理念:\n * - 基于原生 fetch API,零依赖\n * - 拦截器模式遵循 Axios 标准,AI 模型和开发者都熟悉\n * - 可在任何 Node.js 环境使用(NestJS, Express, Koa, 纯 Node)\n *\n * @example\n * ```typescript\n * const client = new HttpClient({\n * baseURL: 'https://api.example.com',\n * timeout: 5000,\n * });\n *\n * // 添加请求拦截器(类似 Axios)\n * client.interceptors.request.use(\n * config => {\n * config.headers = config.headers || {};\n * config.headers['Authorization'] = 'Bearer token';\n * return config;\n * },\n * error => Promise.reject(error)\n * );\n *\n * // 添加响应拦截器(类似 Axios)\n * client.interceptors.response.use(\n * response => {\n * console.log('Response:', response.status);\n * return response;\n * },\n * error => {\n * if (error.response?.status === 401) {\n * // 处理未授权\n * }\n * return Promise.reject(error);\n * }\n * );\n *\n * // 发起请求\n * const response = await client.get('/users');\n * console.log(response.data);\n * ```\n */\nexport class HttpClient {\n private defaultConfig: RequestConfig;\n private securityConfig: Required<SecurityConfig>;\n /**\n * 拦截器管理器(API 与 Axios 一致)\n */\n public interceptors = {\n request: new InterceptorManager<RequestInterceptor>(),\n response: new InterceptorManager<ResponseInterceptor>(),\n };\n\n constructor(config?: HttpClientConfig) {\n const { platform, security, ...restConfig } = config || {};\n\n this.defaultConfig = {\n timeout: 5000,\n ...restConfig,\n };\n\n // 初始化安全配置\n const strictMode = security?.strictMode ?? false;\n this.securityConfig = {\n allowedProtocols: strictMode ? ['http:', 'https:'] : null,\n maxResponseSize: strictMode ? 50 * 1024 * 1024 : 0, // 50MB\n strictMode,\n ...security,\n };\n\n if (platform?.enabled) {\n if (!this.defaultConfig.baseURL) {\n this.defaultConfig.baseURL = resolvePlatformBaseURL(platform);\n }\n registerPlatformPlugin(this.interceptors, platform);\n }\n }\n\n /**\n * GET 请求\n */\n async get(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'GET' });\n }\n\n /**\n * POST 请求\n */\n async post(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'POST',\n body,\n headers,\n });\n }\n\n /**\n * PUT 请求\n */\n async put(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PUT',\n body,\n headers,\n });\n }\n\n /**\n * DELETE 请求\n */\n async delete(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'DELETE' });\n }\n\n /**\n * PATCH 请求\n */\n async patch(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PATCH',\n body,\n headers,\n });\n }\n\n /**\n * 核心请求方法(fetch 风格)\n */\n async request(config: RequestConfig): Promise<Response> {\n // 1. 合并配置\n // Perform a case-insensitive header merge that preserves the final key's casing.\n const baseHeaders = normalizeHeaders(this.defaultConfig.headers);\n const overrideHeaders = normalizeHeaders(config.headers);\n const mergedKeyMap: Record<string, { key: string; value: string }> = {};\n\n for (const key in baseHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: baseHeaders[key] };\n }\n for (const key in overrideHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: overrideHeaders[key] };\n }\n\n const finalHeaders: Record<string, string> = {};\n for (const lowerKey in mergedKeyMap) {\n const { key, value } = mergedKeyMap[lowerKey];\n finalHeaders[key] = value;\n }\n\n let mergedConfig: RequestConfig = {\n ...this.defaultConfig,\n ...config,\n headers: finalHeaders,\n };\n\n // 2. 执行请求拦截器\n try {\n mergedConfig = await this.runRequestInterceptors(mergedConfig);\n } catch (error) {\n return Promise.reject(error);\n }\n\n // 3. 统一 normalize headers(拦截器可能返回 Headers 对象)\n mergedConfig.headers = normalizeHeaders(mergedConfig.headers);\n\n // 4. 构建完整 URL\n const fullUrl = this.buildUrl(mergedConfig.url!, mergedConfig.params);\n\n // 5. 设置超时和取消\n const timeout = mergedConfig.timeout || this.defaultConfig.timeout || 5000;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n // 监听外部 signal\n const externalSignal = mergedConfig.signal;\n if (externalSignal) {\n // 如果外部 signal 已经终止,立即终止\n if (externalSignal.aborted) {\n clearTimeout(timeoutId);\n controller.abort();\n } else {\n // 使用 { once: true } 避免内存泄漏\n externalSignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n }\n\n // 6. 准备 fetch 配置(排除自定义字段)\n const { platformAuth, params, timeout: _timeout, baseURL, url, ...fetchOptions } = mergedConfig;\n\n try {\n // 7. 发起请求\n const response = await fetch(fullUrl, {\n ...fetchOptions,\n signal: controller.signal, // 传递我们自己的 controller signal\n });\n\n clearTimeout(timeoutId);\n\n // 安全检查:响应体大小限制\n if (this.securityConfig.maxResponseSize > 0) {\n const contentLength = response.headers.get('content-length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > this.securityConfig.maxResponseSize) {\n throw new Error(\n `Response size ${size} bytes exceeds limit of ${this.securityConfig.maxResponseSize} bytes`\n );\n }\n }\n }\n\n // 检查响应状态\n if (!response.ok) {\n const error = new HttpError(response, mergedConfig);\n return this.runResponseInterceptors(Promise.reject(error));\n }\n\n // 执行响应拦截器(直接返回 Response)\n return this.runResponseInterceptors(Promise.resolve(response));\n } catch (error) {\n clearTimeout(timeoutId);\n\n // 网络错误或超时错误\n const httpError =\n error instanceof HttpError\n ? error\n : new HttpError(\n undefined,\n mergedConfig,\n (error as Error).name === 'AbortError'\n ? 'Request aborted'\n : (error as Error).message,\n );\n\n return this.runResponseInterceptors(Promise.reject(httpError));\n }\n }\n\n /**\n * 执行请求拦截器链\n */\n private runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {\n let promise: Promise<RequestConfig> = Promise.resolve(config);\n\n const interceptors: RequestInterceptor[] = [];\n this.interceptors.request.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n promise = promise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return promise;\n }\n\n /**\n * 执行响应拦截器链\n */\n private async runResponseInterceptors(\n promise: Promise<Response>,\n ): Promise<Response> {\n let currentPromise = promise;\n\n // 按顺序执行所有响应拦截器\n const interceptors: ResponseInterceptor[] = [];\n this.interceptors.response.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n currentPromise = currentPromise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return currentPromise;\n }\n\n /**\n * 构建完整 URL(包含 baseURL 和 query params)\n */\n private buildUrl(url: string, params?: Record<string, any>): string {\n const baseURL = this.defaultConfig.baseURL;\n\n // 拼接 baseURL 和 url(Axios 风格)\n // - 如果 url 是绝对路径(含协议),直接使用 url\n // - 否则拼接 baseURL 和 url,正确处理斜杠\n let combinedUrl: string;\n if (!baseURL || /^https?:\\/\\//i.test(url)) {\n combinedUrl = url;\n } else {\n combinedUrl = baseURL.replace(/\\/+$/, '') + '/' + url.replace(/^\\/+/, '');\n }\n\n const fullUrl = new URL(combinedUrl);\n\n // 协议白名单检查\n const { allowedProtocols } = this.securityConfig;\n if (allowedProtocols && !allowedProtocols.includes(fullUrl.protocol)) {\n throw new Error(\n `Protocol ${fullUrl.protocol} is not allowed. Allowed: ${allowedProtocols.join(', ')}`\n );\n }\n\n // 合并 query params\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n fullUrl.searchParams.set(key, String(value));\n }\n }\n\n return fullUrl.href;\n }\n}\n"],"mappings":"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","names":["normalized"]}
package/dist/index.mjs CHANGED
@@ -170,6 +170,9 @@ var JWTTokenManager = class {
170
170
  var DEFAULT_DOMAIN_ENV = "FORCE_AUTHN_INNERAPI_DOMAIN";
171
171
  var DEFAULT_ACCESS_KEY_ENV = "FORCE_AUTHN_ACCESS_KEY";
172
172
  var DEFAULT_SECRET_KEY_ENV = "FORCE_AUTHN_ACCESS_SECRET";
173
+ var DEFAULT_ADMIN_DOMAIN_ENV = "MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX";
174
+ var DEFAULT_CLIENT_TOKEN_ENV = "MIAODA_AUTHN_CODE";
175
+ var MIAODA_CLIENT_TOKEN_HEADER = "X-Miaoda-Client-Token";
173
176
  function resolvePlatformBaseURL(options) {
174
177
  if (!options?.enabled) {
175
178
  return options?.baseURL;
@@ -177,7 +180,7 @@ function resolvePlatformBaseURL(options) {
177
180
  if (options.baseURL) {
178
181
  return options.baseURL;
179
182
  }
180
- const envName = options.domainEnv || DEFAULT_DOMAIN_ENV;
183
+ const envName = options.adminInnerApi ? options.adminDomainEnv || DEFAULT_ADMIN_DOMAIN_ENV : options.domainEnv || DEFAULT_DOMAIN_ENV;
181
184
  const domain = process.env[envName];
182
185
  if (!domain) {
183
186
  throw new Error(`\u5E73\u53F0\u6A21\u5F0F\u9700\u8981\u57FA\u7840\u57DF\u540D\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${envName}`);
@@ -200,6 +203,7 @@ function registerPlatformPlugin(interceptors, options) {
200
203
  expireTimeMs,
201
204
  refreshBeforeMs: options.refreshBeforeMs
202
205
  });
206
+ const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;
203
207
  interceptors.request.use((config) => {
204
208
  ensureNoAccessKeyOverride("request.platformAuth.customClaims", config.platformAuth?.customClaims);
205
209
  const claims = {
@@ -213,7 +217,12 @@ function registerPlatformPlugin(interceptors, options) {
213
217
  "Authorization": `Bearer ${token}`,
214
218
  "x-api-key": accessKey
215
219
  };
216
- console.log(111111111, headers);
220
+ if (options.adminInnerApi) {
221
+ const clientToken = process.env[clientTokenEnv];
222
+ if (clientToken) {
223
+ headers[MIAODA_CLIENT_TOKEN_HEADER] = clientToken;
224
+ }
225
+ }
217
226
  return {
218
227
  ...config,
219
228
  headers
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helper/jwt.ts","../src/helper/jwt-manager.ts","../src/plugins/platform-plugin.ts","../src/core/InterceptorManager.ts","../src/core/utils.ts","../src/core/types.ts","../src/core/HttpClient.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'crypto';\n\nexport interface CustomClaims {\n tenant_id?: number;\n user_id?: string;\n app_id?: string;\n app_env?: string;\n sandbox_id?: string;\n access_key: string;\n}\n\nexport interface JWTClaims extends CustomClaims {\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n jti: string;\n [key: string]: any;\n}\n\nexport interface JWTConfig {\n accessKey: string;\n secretKey: string;\n expireTimeMs: number;\n}\n\nexport interface JWTVerifyOptions {\n /**\n * 时钟容差(秒),用于处理服务器时间不同步的情况\n * 默认 60 秒\n */\n clockTolerance?: number;\n\n /**\n * 是否跳过过期验证(仅用于调试)\n * 默认 false\n */\n skipExpiration?: boolean;\n}\n\nexport const DEFAULT_JWT_EXPIRE_TIME_MS = 30 * 60 * 1000;\nexport const DEFAULT_CLOCK_TOLERANCE_SEC = 60;\n\nconst JWT_HEADER = {\n alg: 'HS256',\n typ: 'JWT',\n};\n\nexport function generateJWTToken(\n customClaims: CustomClaims,\n config: JWTConfig,\n): string {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const payload: JWTClaims = {\n ...customClaims,\n iss: customClaims.access_key,\n iat: nowSeconds,\n nbf: nowSeconds,\n exp: nowSeconds + Math.floor(config.expireTimeMs / 1000),\n jti: randomUUID(),\n };\n\n const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));\n const encodedPayload = base64UrlEncode(JSON.stringify(payload));\n const signature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n return `${encodedHeader}.${encodedPayload}.${signature}`;\n}\n\nexport function parseJWTTokenWithVerify(\n tokenString: string,\n config: JWTConfig,\n options?: JWTVerifyOptions,\n): JWTClaims {\n const segments = tokenString.split('.');\n if (segments.length !== 3) {\n throw new Error('invalid JWT token format');\n }\n\n const [encodedHeader, encodedPayload, signature] = segments;\n\n // 1. 验证 header\n const headerJson = JSON.parse(base64UrlDecode(encodedHeader));\n if (headerJson.alg !== 'HS256') {\n throw new Error('unsupported JWT alg');\n }\n\n // 2. 验证签名\n const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n const expectedBuffer = Buffer.from(expectedSignature);\n const signatureBuffer = Buffer.from(signature);\n if (\n expectedBuffer.length !== signatureBuffer.length ||\n !timingSafeEqual(expectedBuffer, signatureBuffer)\n ) {\n throw new Error('JWT signature verification failed');\n }\n\n // 3. 解析 payload\n const payload = JSON.parse(base64UrlDecode(encodedPayload)) as JWTClaims;\n\n // 4. 验证时间声明\n if (!options?.skipExpiration) {\n const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;\n const now = Math.floor(Date.now() / 1000);\n\n // 验证 exp (过期时间)\n if (payload.exp !== undefined) {\n if (payload.exp + clockTolerance < now) {\n throw new Error(\n `JWT token expired at ${new Date(payload.exp * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 nbf (生效时间)\n if (payload.nbf !== undefined) {\n if (payload.nbf - clockTolerance > now) {\n throw new Error(\n `JWT token not yet valid, will be valid at ${new Date(payload.nbf * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 iat (签发时间) - 防止未来时间的 token\n if (payload.iat !== undefined) {\n if (payload.iat - clockTolerance > now) {\n throw new Error(\n `JWT token issued in the future at ${new Date(payload.iat * 1000).toISOString()}`,\n );\n }\n }\n }\n\n return payload;\n}\n\nfunction sign(input: string, secret: string): string {\n return createHmac('sha256', secret)\n .update(input)\n .digest('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlEncode(input: string): string {\n return Buffer.from(input)\n .toString('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlDecode(input: string): string {\n const padLength = (4 - (input.length % 4 || 4)) % 4;\n const padded = `${input}${'='.repeat(padLength)}`\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n return Buffer.from(padded, 'base64').toString('utf8');\n}\n","import { CustomClaims, JWTConfig, generateJWTToken } from './jwt';\n\ninterface CachedToken {\n token: string;\n expiresAtMs: number;\n}\n\ninterface JWTManagerConfig extends JWTConfig {\n /**\n * 在过期前多久刷新 token(毫秒)\n * 默认 5 分钟\n */\n refreshBeforeMs?: number;\n}\n\n/**\n * JWT Token 管理器,负责 token 的生成、缓存和自动刷新\n *\n * 缓存策略:\n * - 基于 claims 的哈希值作为缓存 key(支持多租户+多用户)\n * - 在 token 过期前 refreshBeforeMs 时间内自动刷新\n *\n * @example\n * ```typescript\n * const manager = new JWTTokenManager({\n * accessKey: 'your-access-key',\n * secretKey: 'your-secret-key',\n * expireTimeMs: 30 * 60 * 1000,\n * refreshBeforeMs: 5 * 60 * 1000,\n * });\n *\n * // 获取 token(会自动缓存)\n * const token = manager.getToken({\n * access_key: 'your-access-key',\n * tenant_id: 1001,\n * user_id: 'user123',\n * });\n * ```\n */\nexport class JWTTokenManager {\n private cache = new Map<string, CachedToken>();\n private config: Required<JWTManagerConfig>;\n\n constructor(config: JWTManagerConfig) {\n this.config = {\n refreshBeforeMs: 5 * 60 * 1000, // 默认 5 分钟\n ...config,\n };\n }\n\n /**\n * 获取 JWT token,优先使用缓存\n *\n * 如果缓存的 token 即将过期(距离过期时间小于 refreshBeforeMs),\n * 则会生成新的 token 并更新缓存\n *\n * @param claims - JWT claims\n * @returns JWT token 字符串\n */\n getToken(claims: CustomClaims): string {\n const cacheKey = this.getCacheKey(claims);\n const cached = this.cache.get(cacheKey);\n const now = Date.now();\n\n // 检查缓存是否有效(未过期且不需要刷新)\n if (cached && cached.expiresAtMs - now > this.config.refreshBeforeMs) {\n return cached.token;\n }\n\n // 生成新 token\n const token = generateJWTToken(claims, this.config);\n const expiresAtMs = now + this.config.expireTimeMs;\n\n this.cache.set(cacheKey, { token, expiresAtMs });\n\n return token;\n }\n\n /**\n * 清除所有缓存的 token\n */\n clearCache(): void {\n this.cache.clear();\n }\n\n /**\n * 清除特定 claims 的缓存\n *\n * @param claims - 要清除的 claims\n */\n clearCacheFor(claims: CustomClaims): void {\n const cacheKey = this.getCacheKey(claims);\n this.cache.delete(cacheKey);\n }\n\n /**\n * 生成缓存 key(基于关键的 claims 字段)\n *\n * 使用稳定的字段顺序确保相同 claims 生成相同 key\n *\n * @private\n */\n private getCacheKey(claims: CustomClaims): string {\n // 使用稳定的字段顺序确保相同 claims 生成相同 key\n const parts = [\n claims.access_key,\n claims.tenant_id?.toString() || '',\n claims.user_id || '',\n claims.app_id || '',\n claims.app_env || '',\n claims.sandbox_id || '',\n ];\n return parts.join(':');\n }\n\n /**\n * 获取缓存统计信息(用于监控和调试)\n *\n * @returns 缓存统计信息\n */\n getCacheStats() {\n const now = Date.now();\n let validCount = 0;\n let expiredCount = 0;\n\n for (const cached of this.cache.values()) {\n if (cached.expiresAtMs > now) {\n validCount++;\n } else {\n expiredCount++;\n }\n }\n\n return {\n total: this.cache.size,\n valid: validCount,\n expired: expiredCount,\n };\n }\n}\n","import type { RequestConfig, RequestInterceptor } from '../core/types';\nimport {\n DEFAULT_JWT_EXPIRE_TIME_MS,\n CustomClaims,\n} from '../helper/jwt';\nimport { JWTTokenManager } from '../helper/jwt-manager';\n\nexport type PlatformClaims = Omit<CustomClaims, 'access_key'>;\n\nexport interface PlatformPluginOptions {\n enabled?: boolean;\n baseURL?: string;\n domainEnv?: string;\n accessKeyEnv?: string;\n secretKeyEnv?: string;\n accessKey?: string;\n secretKey?: string;\n expireTimeMs?: number;\n refreshBeforeMs?: number; // 新增:在过期前多久刷新 token\n defaultClaims?: PlatformClaims;\n}\n\nexport interface PlatformRequestAuthConfig {\n customClaims?: PlatformClaims;\n}\n\nconst DEFAULT_DOMAIN_ENV = 'FORCE_AUTHN_INNERAPI_DOMAIN';\nconst DEFAULT_ACCESS_KEY_ENV = 'FORCE_AUTHN_ACCESS_KEY';\nconst DEFAULT_SECRET_KEY_ENV = 'FORCE_AUTHN_ACCESS_SECRET';\n\nexport function resolvePlatformBaseURL(options?: PlatformPluginOptions): string | undefined {\n if (!options?.enabled) {\n return options?.baseURL;\n }\n\n if (options.baseURL) {\n return options.baseURL;\n }\n\n const envName = options.domainEnv || DEFAULT_DOMAIN_ENV;\n const domain = process.env[envName];\n if (!domain) {\n throw new Error(`平台模式需要基础域名,请设置环境变量 ${envName}`);\n }\n return domain;\n}\n\ntype RequestInterceptorManager = {\n use: (\n onFulfilled?: RequestInterceptor['onFulfilled'],\n onRejected?: RequestInterceptor['onRejected'],\n ) => number;\n eject: (id: number) => void;\n};\n\ninterface InterceptorHost {\n request: RequestInterceptorManager;\n}\n\nexport function registerPlatformPlugin(\n interceptors: InterceptorHost,\n options: PlatformPluginOptions,\n): JWTTokenManager | undefined {\n if (!options.enabled) {\n return undefined;\n }\n\n ensureNoAccessKeyOverride('platform.defaultClaims', options.defaultClaims);\n\n // 提前检查 AK/SK,避免每次请求时检查\n const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;\n const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;\n\n const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || '');\n // if (!accessKey) {\n // throw new Error(`平台鉴权需要 access key,请设置环境变量 ${accessKeyEnv}`);\n // }\n\n const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n // if (!secretKey) {\n // throw new Error(`平台鉴权需要 secret key,请设置环境变量 ${secretKeyEnv}`);\n // }\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // 创建 JWT Token Manager(支持缓存和自动刷新)\n const tokenManager = new JWTTokenManager({\n accessKey,\n secretKey,\n expireTimeMs,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n\n // 注册请求拦截器\n interceptors.request.use((config: RequestConfig) => {\n ensureNoAccessKeyOverride('request.platformAuth.customClaims', config.platformAuth?.customClaims);\n\n const claims: CustomClaims = {\n ...(options.defaultClaims || {}),\n ...(config.platformAuth?.customClaims || {}),\n access_key: accessKey,\n };\n\n // 使用 token manager 获取缓存的 token\n const token = tokenManager.getToken(claims);\n\n // 统一使用 Record<string, string> 类型,由 client.ts 的 normalizeHeaders 处理\n const headers = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${token}`,\n 'x-api-key': accessKey,\n };\n\n console.log(111111111, headers)\n\n return {\n ...config,\n headers,\n };\n });\n\n // 返回 token manager 供外部使用(如手动清理缓存)\n return tokenManager;\n}\n\nfunction ensureNoAccessKeyOverride(name: string, claims?: PlatformClaims) {\n if (claims && Object.prototype.hasOwnProperty.call(claims, 'access_key')) {\n throw new Error(`${name} 不允许设置 access_key`);\n }\n}\n","/**\n * 拦截器管理器(模仿 Axios 的 InterceptorManager)\n */\nexport class InterceptorManager<T> {\n private interceptors: (T | null)[] = [];\n\n /**\n * 添加拦截器\n * @returns 拦截器 ID,用于后续移除\n */\n use(onFulfilled?: any, onRejected?: any): number {\n this.interceptors.push({ onFulfilled, onRejected } as T);\n return this.interceptors.length - 1;\n }\n\n /**\n * 移除拦截器\n */\n eject(id: number): void {\n if (this.interceptors[id]) {\n this.interceptors[id] = null;\n }\n }\n\n /**\n * 清空所有拦截器\n */\n clear(): void {\n this.interceptors = [];\n }\n\n /**\n * 遍历所有拦截器\n */\n forEach(fn: (interceptor: T) => void): void {\n this.interceptors.forEach((interceptor) => {\n if (interceptor !== null) {\n fn(interceptor);\n }\n });\n }\n}\n","/**\n * Checks if a value is a plain object.\n * @param value The value to check.\n * @returns True if the value is a plain object, false otherwise.\n */\nexport function isPlainObject(value: any): boolean {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nexport function normalizeHeaders(headers?: RequestInit['headers']): Record<string, string> {\n if (!headers) {\n return {};\n }\n\n if (headers instanceof Headers) {\n const normalized: Record<string, string> = {};\n headers.forEach((value, key) => {\n normalized[key] = value;\n });\n return normalized;\n }\n\n if (Array.isArray(headers)) {\n return headers.reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = value;\n return acc;\n }, {});\n }\n\n const normalized: Record<string, string> = {};\n Object.entries(headers as Record<string, string | readonly string[]>).forEach(\n ([key, value]) => {\n const normalizedValue = Array.isArray(value) ? value.join(',') : value;\n normalized[key] = normalizedValue as string;\n },\n );\n return normalized;\n}\n","import type { PlatformPluginOptions, PlatformRequestAuthConfig } from '../plugins/platform-plugin';\nimport { normalizeHeaders } from './utils';\n\n/**\n * 清理后的请求配置(移除敏感信息)\n */\nexport interface SafeRequestConfig extends Omit<RequestConfig, 'headers'> {\n headers?: Record<string, string>; // 已清理的 headers\n}\n\n/**\n * HTTP 错误对象\n *\n * 注意:为了安全考虑,error.config 中的敏感 headers (如 Authorization, x-api-key)\n * 会被替换为 '[REDACTED]'\n */\nexport class HttpError extends Error {\n public readonly isHttpError: boolean = true;\n public response: Response | undefined;\n public config: SafeRequestConfig;\n\n constructor(\n response: Response | undefined,\n config: RequestConfig,\n message?: string,\n ) {\n super(message || `Request failed with status ${response?.status || 'unknown'}`);\n this.name = 'HttpError';\n this.response = response;\n this.config = sanitizeConfig(config);\n Object.setPrototypeOf(this, HttpError.prototype);\n }\n}\n\nexport interface RequestConfig extends RequestInit {\n url?: string;\n params?: Record<string, any>;\n timeout?: number;\n baseURL?: string;\n platformAuth?: PlatformRequestAuthConfig;\n}\n\nexport interface SecurityConfig {\n /**\n * 允许的 URL 协议白名单\n * 默认: null (不限制)\n * 设置为 ['http:', 'https:'] 可限制只允许 HTTP/HTTPS\n */\n allowedProtocols?: string[] | null;\n\n /**\n * 最大响应体大小(字节)\n * 默认: 0 (不限制)\n * 设置为正数可限制响应体大小,防止内存耗尽攻击\n */\n maxResponseSize?: number;\n\n /**\n * 是否启用严格模式(启用所有安全检查)\n * 默认: false\n * 启用后会自动设置:\n * - allowedProtocols = ['http:', 'https:']\n * - maxResponseSize = 50MB\n */\n strictMode?: boolean;\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n}\n\n/**\n * 请求拦截器函数\n */\nexport type RequestInterceptor = {\n onFulfilled?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 响应拦截器函数(fetch 风格)\n */\nexport type ResponseInterceptor = {\n onFulfilled?: (response: Response) => Response | Promise<Response>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 清理配置中的敏感信息\n */\nfunction sanitizeConfig(config: RequestConfig): SafeRequestConfig {\n const { headers, ...rest } = config;\n\n return {\n ...rest,\n headers: sanitizeHeaders(headers),\n };\n}\n\n/**\n * 清理敏感 headers\n *\n * 将 Authorization, x-api-key, cookie 等敏感字段替换为 '[REDACTED]'\n */\nfunction sanitizeHeaders(headers?: RequestInit['headers']): Record<string, string> | undefined {\n if (!headers) {\n return undefined;\n }\n\n const normalized = normalizeHeaders(headers);\n const sanitized: Record<string, string> = {};\n\n const sensitiveHeaders = ['authorization', 'x-api-key', 'cookie', 'x-secret'];\n\n for (const [key, value] of Object.entries(normalized)) {\n const lowerKey = key.toLowerCase();\n if (sensitiveHeaders.includes(lowerKey)) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = value;\n }\n }\n\n return sanitized;\n}\n","import { registerPlatformPlugin, resolvePlatformBaseURL } from '../plugins/platform-plugin';\nimport { InterceptorManager } from './InterceptorManager';\nimport {\n HttpError,\n type HttpClientConfig,\n type RequestConfig,\n type RequestInterceptor,\n type ResponseInterceptor,\n type SecurityConfig,\n} from './types';\nimport { isPlainObject, normalizeHeaders } from './utils';\n\n/**\n * 技术栈无关的 HTTP 客户端\n *\n * 设计理念:\n * - 基于原生 fetch API,零依赖\n * - 拦截器模式遵循 Axios 标准,AI 模型和开发者都熟悉\n * - 可在任何 Node.js 环境使用(NestJS, Express, Koa, 纯 Node)\n *\n * @example\n * ```typescript\n * const client = new HttpClient({\n * baseURL: 'https://api.example.com',\n * timeout: 5000,\n * });\n *\n * // 添加请求拦截器(类似 Axios)\n * client.interceptors.request.use(\n * config => {\n * config.headers = config.headers || {};\n * config.headers['Authorization'] = 'Bearer token';\n * return config;\n * },\n * error => Promise.reject(error)\n * );\n *\n * // 添加响应拦截器(类似 Axios)\n * client.interceptors.response.use(\n * response => {\n * console.log('Response:', response.status);\n * return response;\n * },\n * error => {\n * if (error.response?.status === 401) {\n * // 处理未授权\n * }\n * return Promise.reject(error);\n * }\n * );\n *\n * // 发起请求\n * const response = await client.get('/users');\n * console.log(response.data);\n * ```\n */\nexport class HttpClient {\n private defaultConfig: RequestConfig;\n private securityConfig: Required<SecurityConfig>;\n /**\n * 拦截器管理器(API 与 Axios 一致)\n */\n public interceptors = {\n request: new InterceptorManager<RequestInterceptor>(),\n response: new InterceptorManager<ResponseInterceptor>(),\n };\n\n constructor(config?: HttpClientConfig) {\n const { platform, security, ...restConfig } = config || {};\n\n this.defaultConfig = {\n timeout: 5000,\n ...restConfig,\n };\n\n // 初始化安全配置\n const strictMode = security?.strictMode ?? false;\n this.securityConfig = {\n allowedProtocols: strictMode ? ['http:', 'https:'] : null,\n maxResponseSize: strictMode ? 50 * 1024 * 1024 : 0, // 50MB\n strictMode,\n ...security,\n };\n\n if (platform?.enabled) {\n if (!this.defaultConfig.baseURL) {\n this.defaultConfig.baseURL = resolvePlatformBaseURL(platform);\n }\n registerPlatformPlugin(this.interceptors, platform);\n }\n }\n\n /**\n * GET 请求\n */\n async get(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'GET' });\n }\n\n /**\n * POST 请求\n */\n async post(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'POST',\n body,\n headers,\n });\n }\n\n /**\n * PUT 请求\n */\n async put(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PUT',\n body,\n headers,\n });\n }\n\n /**\n * DELETE 请求\n */\n async delete(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'DELETE' });\n }\n\n /**\n * PATCH 请求\n */\n async patch(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PATCH',\n body,\n headers,\n });\n }\n\n /**\n * 核心请求方法(fetch 风格)\n */\n async request(config: RequestConfig): Promise<Response> {\n // 1. 合并配置\n // Perform a case-insensitive header merge that preserves the final key's casing.\n const baseHeaders = normalizeHeaders(this.defaultConfig.headers);\n const overrideHeaders = normalizeHeaders(config.headers);\n const mergedKeyMap: Record<string, { key: string; value: string }> = {};\n\n for (const key in baseHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: baseHeaders[key] };\n }\n for (const key in overrideHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: overrideHeaders[key] };\n }\n\n const finalHeaders: Record<string, string> = {};\n for (const lowerKey in mergedKeyMap) {\n const { key, value } = mergedKeyMap[lowerKey];\n finalHeaders[key] = value;\n }\n\n let mergedConfig: RequestConfig = {\n ...this.defaultConfig,\n ...config,\n headers: finalHeaders,\n };\n\n // 2. 执行请求拦截器\n try {\n mergedConfig = await this.runRequestInterceptors(mergedConfig);\n } catch (error) {\n return Promise.reject(error);\n }\n\n // 3. 统一 normalize headers(拦截器可能返回 Headers 对象)\n mergedConfig.headers = normalizeHeaders(mergedConfig.headers);\n\n // 4. 构建完整 URL\n const fullUrl = this.buildUrl(mergedConfig.url!, mergedConfig.params);\n\n // 5. 设置超时和取消\n const timeout = mergedConfig.timeout || this.defaultConfig.timeout || 5000;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n // 监听外部 signal\n const externalSignal = mergedConfig.signal;\n if (externalSignal) {\n // 如果外部 signal 已经终止,立即终止\n if (externalSignal.aborted) {\n clearTimeout(timeoutId);\n controller.abort();\n } else {\n // 使用 { once: true } 避免内存泄漏\n externalSignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n }\n\n // 6. 准备 fetch 配置(排除自定义字段)\n const { platformAuth, params, timeout: _timeout, baseURL, url, ...fetchOptions } = mergedConfig;\n\n try {\n // 7. 发起请求\n const response = await fetch(fullUrl, {\n ...fetchOptions,\n signal: controller.signal, // 传递我们自己的 controller signal\n });\n\n clearTimeout(timeoutId);\n\n // 安全检查:响应体大小限制\n if (this.securityConfig.maxResponseSize > 0) {\n const contentLength = response.headers.get('content-length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > this.securityConfig.maxResponseSize) {\n throw new Error(\n `Response size ${size} bytes exceeds limit of ${this.securityConfig.maxResponseSize} bytes`\n );\n }\n }\n }\n\n // 检查响应状态\n if (!response.ok) {\n const error = new HttpError(response, mergedConfig);\n return this.runResponseInterceptors(Promise.reject(error));\n }\n\n // 执行响应拦截器(直接返回 Response)\n return this.runResponseInterceptors(Promise.resolve(response));\n } catch (error) {\n clearTimeout(timeoutId);\n\n // 网络错误或超时错误\n const httpError =\n error instanceof HttpError\n ? error\n : new HttpError(\n undefined,\n mergedConfig,\n (error as Error).name === 'AbortError'\n ? 'Request aborted'\n : (error as Error).message,\n );\n\n return this.runResponseInterceptors(Promise.reject(httpError));\n }\n }\n\n /**\n * 执行请求拦截器链\n */\n private runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {\n let promise: Promise<RequestConfig> = Promise.resolve(config);\n\n const interceptors: RequestInterceptor[] = [];\n this.interceptors.request.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n promise = promise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return promise;\n }\n\n /**\n * 执行响应拦截器链\n */\n private async runResponseInterceptors(\n promise: Promise<Response>,\n ): Promise<Response> {\n let currentPromise = promise;\n\n // 按顺序执行所有响应拦截器\n const interceptors: ResponseInterceptor[] = [];\n this.interceptors.response.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n currentPromise = currentPromise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return currentPromise;\n }\n\n /**\n * 构建完整 URL(包含 baseURL 和 query params)\n */\n private buildUrl(url: string, params?: Record<string, any>): string {\n const baseURL = this.defaultConfig.baseURL;\n\n // 拼接 baseURL 和 url(Axios 风格)\n // - 如果 url 是绝对路径(含协议),直接使用 url\n // - 否则拼接 baseURL 和 url,正确处理斜杠\n let combinedUrl: string;\n if (!baseURL || /^https?:\\/\\//i.test(url)) {\n combinedUrl = url;\n } else {\n combinedUrl = baseURL.replace(/\\/+$/, '') + '/' + url.replace(/^\\/+/, '');\n }\n\n const fullUrl = new URL(combinedUrl);\n\n // 协议白名单检查\n const { allowedProtocols } = this.securityConfig;\n if (allowedProtocols && !allowedProtocols.includes(fullUrl.protocol)) {\n throw new Error(\n `Protocol ${fullUrl.protocol} is not allowed. Allowed: ${allowedProtocols.join(', ')}`\n );\n }\n\n // 合并 query params\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n fullUrl.searchParams.set(key, String(value));\n }\n }\n\n return fullUrl.href;\n }\n}\n"],"mappings":";AAAA,SAAS,YAAY,YAAY,uBAAuB;AAwCjD,IAAM,6BAA6B,KAAK,KAAK;AAC7C,IAAM,8BAA8B;AAE3C,IAAM,aAAa;AAAA,EACjB,KAAK;AAAA,EACL,KAAK;AACP;AAEO,SAAS,iBACd,cACA,QACQ;AACR,QAAM,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC/C,QAAM,UAAqB;AAAA,IACzB,GAAG;AAAA,IACH,KAAK,aAAa;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,aAAa,KAAK,MAAM,OAAO,eAAe,GAAI;AAAA,IACvD,KAAK,WAAW;AAAA,EAClB;AAEA,QAAM,gBAAgB,gBAAgB,KAAK,UAAU,UAAU,CAAC;AAChE,QAAM,iBAAiB,gBAAgB,KAAK,UAAU,OAAO,CAAC;AAC9D,QAAM,YAAY,KAAK,GAAG,aAAa,IAAI,cAAc,IAAI,OAAO,SAAS;AAE7E,SAAO,GAAG,aAAa,IAAI,cAAc,IAAI,SAAS;AACxD;AAEO,SAAS,wBACd,aACA,QACA,SACW;AACX,QAAM,WAAW,YAAY,MAAM,GAAG;AACtC,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,QAAM,CAAC,eAAe,gBAAgB,SAAS,IAAI;AAGnD,QAAM,aAAa,KAAK,MAAM,gBAAgB,aAAa,CAAC;AAC5D,MAAI,WAAW,QAAQ,SAAS;AAC9B,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAGA,QAAM,oBAAoB,KAAK,GAAG,aAAa,IAAI,cAAc,IAAI,OAAO,SAAS;AAErF,QAAM,iBAAiB,OAAO,KAAK,iBAAiB;AACpD,QAAM,kBAAkB,OAAO,KAAK,SAAS;AAC7C,MACE,eAAe,WAAW,gBAAgB,UAC1C,CAAC,gBAAgB,gBAAgB,eAAe,GAChD;AACA,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,UAAU,KAAK,MAAM,gBAAgB,cAAc,CAAC;AAG1D,MAAI,CAAC,SAAS,gBAAgB;AAC5B,UAAM,iBAAiB,SAAS,kBAAkB;AAClD,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAGxC,QAAI,QAAQ,QAAQ,QAAW;AAC7B,UAAI,QAAQ,MAAM,iBAAiB,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,wBAAwB,IAAI,KAAK,QAAQ,MAAM,GAAI,EAAE,YAAY,CAAC;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,QAAQ,QAAW;AAC7B,UAAI,QAAQ,MAAM,iBAAiB,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,6CAA6C,IAAI,KAAK,QAAQ,MAAM,GAAI,EAAE,YAAY,CAAC;AAAA,QACzF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,QAAQ,QAAW;AAC7B,UAAI,QAAQ,MAAM,iBAAiB,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,qCAAqC,IAAI,KAAK,QAAQ,MAAM,GAAI,EAAE,YAAY,CAAC;AAAA,QACjF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,KAAK,OAAe,QAAwB;AACnD,SAAO,WAAW,UAAU,MAAM,EAC/B,OAAO,KAAK,EACZ,OAAO,QAAQ,EACf,QAAQ,MAAM,EAAE,EAChB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AACvB;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,OAAO,KAAK,KAAK,EACrB,SAAS,QAAQ,EACjB,QAAQ,MAAM,EAAE,EAChB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AACvB;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,aAAa,KAAK,MAAM,SAAS,KAAK,MAAM;AAClD,QAAM,SAAS,GAAG,KAAK,GAAG,IAAI,OAAO,SAAS,CAAC,GAC5C,QAAQ,MAAM,GAAG,EACjB,QAAQ,MAAM,GAAG;AACpB,SAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,MAAM;AACtD;;;AC1HO,IAAM,kBAAN,MAAsB;AAAA,EACnB,QAAQ,oBAAI,IAAyB;AAAA,EACrC;AAAA,EAER,YAAY,QAA0B;AACpC,SAAK,SAAS;AAAA,MACZ,iBAAiB,IAAI,KAAK;AAAA;AAAA,MAC1B,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAS,QAA8B;AACrC,UAAM,WAAW,KAAK,YAAY,MAAM;AACxC,UAAM,SAAS,KAAK,MAAM,IAAI,QAAQ;AACtC,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,UAAU,OAAO,cAAc,MAAM,KAAK,OAAO,iBAAiB;AACpE,aAAO,OAAO;AAAA,IAChB;AAGA,UAAM,QAAQ,iBAAiB,QAAQ,KAAK,MAAM;AAClD,UAAM,cAAc,MAAM,KAAK,OAAO;AAEtC,SAAK,MAAM,IAAI,UAAU,EAAE,OAAO,YAAY,CAAC;AAE/C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,QAA4B;AACxC,UAAM,WAAW,KAAK,YAAY,MAAM;AACxC,SAAK,MAAM,OAAO,QAAQ;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YAAY,QAA8B;AAEhD,UAAM,QAAQ;AAAA,MACZ,OAAO;AAAA,MACP,OAAO,WAAW,SAAS,KAAK;AAAA,MAChC,OAAO,WAAW;AAAA,MAClB,OAAO,UAAU;AAAA,MACjB,OAAO,WAAW;AAAA,MAClB,OAAO,cAAc;AAAA,IACvB;AACA,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB;AACd,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,aAAa;AACjB,QAAI,eAAe;AAEnB,eAAW,UAAU,KAAK,MAAM,OAAO,GAAG;AACxC,UAAI,OAAO,cAAc,KAAK;AAC5B;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO,KAAK,MAAM;AAAA,MAClB,OAAO;AAAA,MACP,SAAS;AAAA,IACX;AAAA,EACF;AACF;;;ACjHA,IAAM,qBAAqB;AAC3B,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB;AAExB,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,SAAS,SAAS;AACrB,WAAO,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS;AACnB,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,UAAU,QAAQ,aAAa;AACrC,QAAM,SAAS,QAAQ,IAAI,OAAO;AAClC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,gHAAsB,OAAO,EAAE;AAAA,EACjD;AACA,SAAO;AACT;AAcO,SAAS,uBACd,cACA,SAC6B;AAC7B,MAAI,CAAC,QAAQ,SAAS;AACpB,WAAO;AAAA,EACT;AAEA,4BAA0B,0BAA0B,QAAQ,aAAa;AAGzE,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAM,YAAY,QAAQ,cAAc,QAAQ,IAAI,YAAY,KAAK;AAKrE,QAAM,YAAY,QAAQ,cAAc,QAAQ,IAAI,YAAY,KAAK;AAKrE,QAAM,eAAe,QAAQ,gBAAgB;AAG7C,QAAM,eAAe,IAAI,gBAAgB;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB,QAAQ;AAAA,EAC3B,CAAC;AAGD,eAAa,QAAQ,IAAI,CAAC,WAA0B;AAClD,8BAA0B,qCAAqC,OAAO,cAAc,YAAY;AAEhG,UAAM,SAAuB;AAAA,MAC3B,GAAI,QAAQ,iBAAiB,CAAC;AAAA,MAC9B,GAAI,OAAO,cAAc,gBAAgB,CAAC;AAAA,MAC1C,YAAY;AAAA,IACd;AAGA,UAAM,QAAQ,aAAa,SAAS,MAAM;AAG1C,UAAM,UAAU;AAAA,MACd,GAAI,OAAO;AAAA,MACX,iBAAiB,UAAU,KAAK;AAAA,MAChC,aAAa;AAAA,IACf;AAEA,YAAQ,IAAI,WAAW,OAAO;AAE9B,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAGD,SAAO;AACT;AAEA,SAAS,0BAA0B,MAAc,QAAyB;AACxE,MAAI,UAAU,OAAO,UAAU,eAAe,KAAK,QAAQ,YAAY,GAAG;AACxE,UAAM,IAAI,MAAM,GAAG,IAAI,4CAAmB;AAAA,EAC5C;AACF;;;AC9HO,IAAM,qBAAN,MAA4B;AAAA,EACzB,eAA6B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,IAAI,aAAmB,YAA0B;AAC/C,SAAK,aAAa,KAAK,EAAE,aAAa,WAAW,CAAM;AACvD,WAAO,KAAK,aAAa,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAkB;AACtB,QAAI,KAAK,aAAa,EAAE,GAAG;AACzB,WAAK,aAAa,EAAE,IAAI;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,eAAe,CAAC;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,IAAoC;AAC1C,SAAK,aAAa,QAAQ,CAAC,gBAAgB;AACzC,UAAI,gBAAgB,MAAM;AACxB,WAAG,WAAW;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACpCO,SAAS,cAAc,OAAqB;AACjD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,OAAO,eAAe,KAAK;AACzC,SAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEO,SAAS,iBAAiB,SAA0D;AACzF,MAAI,CAAC,SAAS;AACZ,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,mBAAmB,SAAS;AAC9B,UAAMA,cAAqC,CAAC;AAC5C,YAAQ,QAAQ,CAAC,OAAO,QAAQ;AAC9B,MAAAA,YAAW,GAAG,IAAI;AAAA,IACpB,CAAC;AACD,WAAOA;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QAAQ,OAA+B,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM;AACnE,UAAI,GAAG,IAAI;AACX,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AAEA,QAAM,aAAqC,CAAC;AAC5C,SAAO,QAAQ,OAAqD,EAAE;AAAA,IACpE,CAAC,CAAC,KAAK,KAAK,MAAM;AAChB,YAAM,kBAAkB,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;AACjE,iBAAW,GAAG,IAAI;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;;;ACzBO,IAAM,YAAN,MAAM,mBAAkB,MAAM;AAAA,EACnB,cAAuB;AAAA,EAChC;AAAA,EACA;AAAA,EAEP,YACE,UACA,QACA,SACA;AACA,UAAM,WAAW,8BAA8B,UAAU,UAAU,SAAS,EAAE;AAC9E,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,SAAS,eAAe,MAAM;AACnC,WAAO,eAAe,MAAM,WAAU,SAAS;AAAA,EACjD;AACF;AA2DA,SAAS,eAAe,QAA0C;AAChE,QAAM,EAAE,SAAS,GAAG,KAAK,IAAI;AAE7B,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,gBAAgB,OAAO;AAAA,EAClC;AACF;AAOA,SAAS,gBAAgB,SAAsE;AAC7F,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,iBAAiB,OAAO;AAC3C,QAAM,YAAoC,CAAC;AAE3C,QAAM,mBAAmB,CAAC,iBAAiB,aAAa,UAAU,UAAU;AAE5E,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,UAAM,WAAW,IAAI,YAAY;AACjC,QAAI,iBAAiB,SAAS,QAAQ,GAAG;AACvC,gBAAU,GAAG,IAAI;AAAA,IACnB,OAAO;AACL,gBAAU,GAAG,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;;;ACrEO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAID,eAAe;AAAA,IACpB,SAAS,IAAI,mBAAuC;AAAA,IACpD,UAAU,IAAI,mBAAwC;AAAA,EACxD;AAAA,EAEA,YAAY,QAA2B;AACrC,UAAM,EAAE,UAAU,UAAU,GAAG,WAAW,IAAI,UAAU,CAAC;AAEzD,SAAK,gBAAgB;AAAA,MACnB,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAGA,UAAM,aAAa,UAAU,cAAc;AAC3C,SAAK,iBAAiB;AAAA,MACpB,kBAAkB,aAAa,CAAC,SAAS,QAAQ,IAAI;AAAA,MACrD,iBAAiB,aAAa,KAAK,OAAO,OAAO;AAAA;AAAA,MACjD;AAAA,MACA,GAAG;AAAA,IACL;AAEA,QAAI,UAAU,SAAS;AACrB,UAAI,CAAC,KAAK,cAAc,SAAS;AAC/B,aAAK,cAAc,UAAU,uBAAuB,QAAQ;AAAA,MAC9D;AACA,6BAAuB,KAAK,cAAc,QAAQ;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,KAAa,QAA2C;AAChE,WAAO,KAAK,QAAQ,EAAE,GAAG,QAAQ,KAAK,QAAQ,MAAM,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,KAAa,MAAY,QAA2C;AAC7E,UAAM,UAAkC,EAAE,GAAG,QAAQ,QAAQ;AAC7D,QAAI,OAAY;AAEhB,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO,KAAK,UAAU,IAAI;AAC1B,UAAI,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,cAAc,GAAG;AAC7E,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ;AAAA,MAClB,GAAG;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,KAAa,MAAY,QAA2C;AAC5E,UAAM,UAAkC,EAAE,GAAG,QAAQ,QAAQ;AAC7D,QAAI,OAAY;AAEhB,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO,KAAK,UAAU,IAAI;AAC1B,UAAI,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,cAAc,GAAG;AAC7E,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ;AAAA,MAClB,GAAG;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,KAAa,QAA2C;AACnE,WAAO,KAAK,QAAQ,EAAE,GAAG,QAAQ,KAAK,QAAQ,SAAS,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,KAAa,MAAY,QAA2C;AAC9E,UAAM,UAAkC,EAAE,GAAG,QAAQ,QAAQ;AAC7D,QAAI,OAAY;AAEhB,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO,KAAK,UAAU,IAAI;AAC1B,UAAI,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,cAAc,GAAG;AAC7E,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ;AAAA,MAClB,GAAG;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,QAA0C;AAGtD,UAAM,cAAc,iBAAiB,KAAK,cAAc,OAAO;AAC/D,UAAM,kBAAkB,iBAAiB,OAAO,OAAO;AACvD,UAAM,eAA+D,CAAC;AAEtE,eAAW,OAAO,aAAa;AAC7B,mBAAa,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,OAAO,YAAY,GAAG,EAAE;AAAA,IACnE;AACA,eAAW,OAAO,iBAAiB;AACjC,mBAAa,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,OAAO,gBAAgB,GAAG,EAAE;AAAA,IACvE;AAEA,UAAM,eAAuC,CAAC;AAC9C,eAAW,YAAY,cAAc;AACnC,YAAM,EAAE,KAAK,MAAM,IAAI,aAAa,QAAQ;AAC5C,mBAAa,GAAG,IAAI;AAAA,IACtB;AAEA,QAAI,eAA8B;AAAA,MAChC,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,SAAS;AAAA,IACX;AAGA,QAAI;AACF,qBAAe,MAAM,KAAK,uBAAuB,YAAY;AAAA,IAC/D,SAAS,OAAO;AACd,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B;AAGA,iBAAa,UAAU,iBAAiB,aAAa,OAAO;AAG5D,UAAM,UAAU,KAAK,SAAS,aAAa,KAAM,aAAa,MAAM;AAGpE,UAAM,UAAU,aAAa,WAAW,KAAK,cAAc,WAAW;AACtE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAG9D,UAAM,iBAAiB,aAAa;AACpC,QAAI,gBAAgB;AAElB,UAAI,eAAe,SAAS;AAC1B,qBAAa,SAAS;AACtB,mBAAW,MAAM;AAAA,MACnB,OAAO;AAEL,uBAAe,iBAAiB,SAAS,MAAM,WAAW,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,MACnF;AAAA,IACF;AAGA,UAAM,EAAE,cAAc,QAAQ,SAAS,UAAU,SAAS,KAAK,GAAG,aAAa,IAAI;AAEnF,QAAI;AAEF,YAAM,WAAW,MAAM,MAAM,SAAS;AAAA,QACpC,GAAG;AAAA,QACH,QAAQ,WAAW;AAAA;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAGtB,UAAI,KAAK,eAAe,kBAAkB,GAAG;AAC3C,cAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,YAAI,eAAe;AACjB,gBAAM,OAAO,SAAS,eAAe,EAAE;AACvC,cAAI,OAAO,KAAK,eAAe,iBAAiB;AAC9C,kBAAM,IAAI;AAAA,cACR,iBAAiB,IAAI,2BAA2B,KAAK,eAAe,eAAe;AAAA,YACrF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,QAAQ,IAAI,UAAU,UAAU,YAAY;AAClD,eAAO,KAAK,wBAAwB,QAAQ,OAAO,KAAK,CAAC;AAAA,MAC3D;AAGA,aAAO,KAAK,wBAAwB,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IAC/D,SAAS,OAAO;AACd,mBAAa,SAAS;AAGtB,YAAM,YACJ,iBAAiB,YACb,QACA,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACC,MAAgB,SAAS,eACtB,oBACC,MAAgB;AAAA,MACvB;AAEJ,aAAO,KAAK,wBAAwB,QAAQ,OAAO,SAAS,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB,QAA+C;AAC5E,QAAI,UAAkC,QAAQ,QAAQ,MAAM;AAE5D,UAAM,eAAqC,CAAC;AAC5C,SAAK,aAAa,QAAQ,QAAQ,CAAC,gBAAgB;AACjD,mBAAa,KAAK,WAAW;AAAA,IAC/B,CAAC;AAED,eAAW,eAAe,cAAc;AACtC,gBAAU,QAAQ;AAAA,QAChB,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,wBACZ,SACmB;AACnB,QAAI,iBAAiB;AAGrB,UAAM,eAAsC,CAAC;AAC7C,SAAK,aAAa,SAAS,QAAQ,CAAC,gBAAgB;AAClD,mBAAa,KAAK,WAAW;AAAA,IAC/B,CAAC;AAED,eAAW,eAAe,cAAc;AACtC,uBAAiB,eAAe;AAAA,QAC9B,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,KAAa,QAAsC;AAClE,UAAM,UAAU,KAAK,cAAc;AAKnC,QAAI;AACJ,QAAI,CAAC,WAAW,gBAAgB,KAAK,GAAG,GAAG;AACzC,oBAAc;AAAA,IAChB,OAAO;AACL,oBAAc,QAAQ,QAAQ,QAAQ,EAAE,IAAI,MAAM,IAAI,QAAQ,QAAQ,EAAE;AAAA,IAC1E;AAEA,UAAM,UAAU,IAAI,IAAI,WAAW;AAGnC,UAAM,EAAE,iBAAiB,IAAI,KAAK;AAClC,QAAI,oBAAoB,CAAC,iBAAiB,SAAS,QAAQ,QAAQ,GAAG;AACpE,YAAM,IAAI;AAAA,QACR,YAAY,QAAQ,QAAQ,6BAA6B,iBAAiB,KAAK,IAAI,CAAC;AAAA,MACtF;AAAA,IACF;AAGA,QAAI,QAAQ;AACV,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAI,UAAU,UAAa,UAAU,KAAM;AAC3C,gBAAQ,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MAC7C;AAAA,IACF;AAEA,WAAO,QAAQ;AAAA,EACjB;AACF;","names":["normalized"]}
1
+ {"version":3,"sources":["../src/helper/jwt.ts","../src/helper/jwt-manager.ts","../src/plugins/platform-plugin.ts","../src/core/InterceptorManager.ts","../src/core/utils.ts","../src/core/types.ts","../src/core/HttpClient.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'crypto';\n\nexport interface CustomClaims {\n tenant_id?: number;\n user_id?: string;\n app_id?: string;\n app_env?: string;\n sandbox_id?: string;\n access_key: string;\n}\n\nexport interface JWTClaims extends CustomClaims {\n iss: string;\n iat: number;\n nbf: number;\n exp: number;\n jti: string;\n [key: string]: any;\n}\n\nexport interface JWTConfig {\n accessKey: string;\n secretKey: string;\n expireTimeMs: number;\n}\n\nexport interface JWTVerifyOptions {\n /**\n * 时钟容差(秒),用于处理服务器时间不同步的情况\n * 默认 60 秒\n */\n clockTolerance?: number;\n\n /**\n * 是否跳过过期验证(仅用于调试)\n * 默认 false\n */\n skipExpiration?: boolean;\n}\n\nexport const DEFAULT_JWT_EXPIRE_TIME_MS = 30 * 60 * 1000;\nexport const DEFAULT_CLOCK_TOLERANCE_SEC = 60;\n\nconst JWT_HEADER = {\n alg: 'HS256',\n typ: 'JWT',\n};\n\nexport function generateJWTToken(\n customClaims: CustomClaims,\n config: JWTConfig,\n): string {\n const nowSeconds = Math.floor(Date.now() / 1000);\n const payload: JWTClaims = {\n ...customClaims,\n iss: customClaims.access_key,\n iat: nowSeconds,\n nbf: nowSeconds,\n exp: nowSeconds + Math.floor(config.expireTimeMs / 1000),\n jti: randomUUID(),\n };\n\n const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));\n const encodedPayload = base64UrlEncode(JSON.stringify(payload));\n const signature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n return `${encodedHeader}.${encodedPayload}.${signature}`;\n}\n\nexport function parseJWTTokenWithVerify(\n tokenString: string,\n config: JWTConfig,\n options?: JWTVerifyOptions,\n): JWTClaims {\n const segments = tokenString.split('.');\n if (segments.length !== 3) {\n throw new Error('invalid JWT token format');\n }\n\n const [encodedHeader, encodedPayload, signature] = segments;\n\n // 1. 验证 header\n const headerJson = JSON.parse(base64UrlDecode(encodedHeader));\n if (headerJson.alg !== 'HS256') {\n throw new Error('unsupported JWT alg');\n }\n\n // 2. 验证签名\n const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);\n\n const expectedBuffer = Buffer.from(expectedSignature);\n const signatureBuffer = Buffer.from(signature);\n if (\n expectedBuffer.length !== signatureBuffer.length ||\n !timingSafeEqual(expectedBuffer, signatureBuffer)\n ) {\n throw new Error('JWT signature verification failed');\n }\n\n // 3. 解析 payload\n const payload = JSON.parse(base64UrlDecode(encodedPayload)) as JWTClaims;\n\n // 4. 验证时间声明\n if (!options?.skipExpiration) {\n const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;\n const now = Math.floor(Date.now() / 1000);\n\n // 验证 exp (过期时间)\n if (payload.exp !== undefined) {\n if (payload.exp + clockTolerance < now) {\n throw new Error(\n `JWT token expired at ${new Date(payload.exp * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 nbf (生效时间)\n if (payload.nbf !== undefined) {\n if (payload.nbf - clockTolerance > now) {\n throw new Error(\n `JWT token not yet valid, will be valid at ${new Date(payload.nbf * 1000).toISOString()}`,\n );\n }\n }\n\n // 验证 iat (签发时间) - 防止未来时间的 token\n if (payload.iat !== undefined) {\n if (payload.iat - clockTolerance > now) {\n throw new Error(\n `JWT token issued in the future at ${new Date(payload.iat * 1000).toISOString()}`,\n );\n }\n }\n }\n\n return payload;\n}\n\nfunction sign(input: string, secret: string): string {\n return createHmac('sha256', secret)\n .update(input)\n .digest('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlEncode(input: string): string {\n return Buffer.from(input)\n .toString('base64')\n .replace(/=/g, '')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_');\n}\n\nfunction base64UrlDecode(input: string): string {\n const padLength = (4 - (input.length % 4 || 4)) % 4;\n const padded = `${input}${'='.repeat(padLength)}`\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n return Buffer.from(padded, 'base64').toString('utf8');\n}\n","import { CustomClaims, JWTConfig, generateJWTToken } from './jwt';\n\ninterface CachedToken {\n token: string;\n expiresAtMs: number;\n}\n\ninterface JWTManagerConfig extends JWTConfig {\n /**\n * 在过期前多久刷新 token(毫秒)\n * 默认 5 分钟\n */\n refreshBeforeMs?: number;\n}\n\n/**\n * JWT Token 管理器,负责 token 的生成、缓存和自动刷新\n *\n * 缓存策略:\n * - 基于 claims 的哈希值作为缓存 key(支持多租户+多用户)\n * - 在 token 过期前 refreshBeforeMs 时间内自动刷新\n *\n * @example\n * ```typescript\n * const manager = new JWTTokenManager({\n * accessKey: 'your-access-key',\n * secretKey: 'your-secret-key',\n * expireTimeMs: 30 * 60 * 1000,\n * refreshBeforeMs: 5 * 60 * 1000,\n * });\n *\n * // 获取 token(会自动缓存)\n * const token = manager.getToken({\n * access_key: 'your-access-key',\n * tenant_id: 1001,\n * user_id: 'user123',\n * });\n * ```\n */\nexport class JWTTokenManager {\n private cache = new Map<string, CachedToken>();\n private config: Required<JWTManagerConfig>;\n\n constructor(config: JWTManagerConfig) {\n this.config = {\n refreshBeforeMs: 5 * 60 * 1000, // 默认 5 分钟\n ...config,\n };\n }\n\n /**\n * 获取 JWT token,优先使用缓存\n *\n * 如果缓存的 token 即将过期(距离过期时间小于 refreshBeforeMs),\n * 则会生成新的 token 并更新缓存\n *\n * @param claims - JWT claims\n * @returns JWT token 字符串\n */\n getToken(claims: CustomClaims): string {\n const cacheKey = this.getCacheKey(claims);\n const cached = this.cache.get(cacheKey);\n const now = Date.now();\n\n // 检查缓存是否有效(未过期且不需要刷新)\n if (cached && cached.expiresAtMs - now > this.config.refreshBeforeMs) {\n return cached.token;\n }\n\n // 生成新 token\n const token = generateJWTToken(claims, this.config);\n const expiresAtMs = now + this.config.expireTimeMs;\n\n this.cache.set(cacheKey, { token, expiresAtMs });\n\n return token;\n }\n\n /**\n * 清除所有缓存的 token\n */\n clearCache(): void {\n this.cache.clear();\n }\n\n /**\n * 清除特定 claims 的缓存\n *\n * @param claims - 要清除的 claims\n */\n clearCacheFor(claims: CustomClaims): void {\n const cacheKey = this.getCacheKey(claims);\n this.cache.delete(cacheKey);\n }\n\n /**\n * 生成缓存 key(基于关键的 claims 字段)\n *\n * 使用稳定的字段顺序确保相同 claims 生成相同 key\n *\n * @private\n */\n private getCacheKey(claims: CustomClaims): string {\n // 使用稳定的字段顺序确保相同 claims 生成相同 key\n const parts = [\n claims.access_key,\n claims.tenant_id?.toString() || '',\n claims.user_id || '',\n claims.app_id || '',\n claims.app_env || '',\n claims.sandbox_id || '',\n ];\n return parts.join(':');\n }\n\n /**\n * 获取缓存统计信息(用于监控和调试)\n *\n * @returns 缓存统计信息\n */\n getCacheStats() {\n const now = Date.now();\n let validCount = 0;\n let expiredCount = 0;\n\n for (const cached of this.cache.values()) {\n if (cached.expiresAtMs > now) {\n validCount++;\n } else {\n expiredCount++;\n }\n }\n\n return {\n total: this.cache.size,\n valid: validCount,\n expired: expiredCount,\n };\n }\n}\n","import type { RequestConfig, RequestInterceptor } from '../core/types';\nimport {\n DEFAULT_JWT_EXPIRE_TIME_MS,\n CustomClaims,\n} from '../helper/jwt';\nimport { JWTTokenManager } from '../helper/jwt-manager';\n\nexport type PlatformClaims = Omit<CustomClaims, 'access_key'>;\n\nexport interface PlatformPluginOptions {\n enabled?: boolean;\n baseURL?: string;\n domainEnv?: string;\n accessKeyEnv?: string;\n secretKeyEnv?: string;\n accessKey?: string;\n secretKey?: string;\n expireTimeMs?: number;\n refreshBeforeMs?: number; // 新增:在过期前多久刷新 token\n defaultClaims?: PlatformClaims;\n /**\n * 管理端 innerapi 链路(仅用于妙搭开发态)\n * 启用后:\n * - baseURL 改从 adminDomainEnv(默认 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX)读取\n * - 每次请求从 clientTokenEnv(默认 MIAODA_AUTHN_CODE)读取用户凭证,\n * 注入为 X-Miaoda-Client-Token 请求头(读不到则不注入)\n * - AK/SK 的 Authorization + x-api-key 照旧\n */\n adminInnerApi?: boolean;\n adminDomainEnv?: string;\n clientTokenEnv?: string;\n}\n\nexport interface PlatformRequestAuthConfig {\n customClaims?: PlatformClaims;\n}\n\nconst DEFAULT_DOMAIN_ENV = 'FORCE_AUTHN_INNERAPI_DOMAIN';\nconst DEFAULT_ACCESS_KEY_ENV = 'FORCE_AUTHN_ACCESS_KEY';\nconst DEFAULT_SECRET_KEY_ENV = 'FORCE_AUTHN_ACCESS_SECRET';\nconst DEFAULT_ADMIN_DOMAIN_ENV = 'MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX';\nconst DEFAULT_CLIENT_TOKEN_ENV = 'MIAODA_AUTHN_CODE';\nconst MIAODA_CLIENT_TOKEN_HEADER = 'X-Miaoda-Client-Token';\n\nexport function resolvePlatformBaseURL(options?: PlatformPluginOptions): string | undefined {\n if (!options?.enabled) {\n return options?.baseURL;\n }\n\n if (options.baseURL) {\n return options.baseURL;\n }\n\n const envName = options.adminInnerApi\n ? (options.adminDomainEnv || DEFAULT_ADMIN_DOMAIN_ENV)\n : (options.domainEnv || DEFAULT_DOMAIN_ENV);\n const domain = process.env[envName];\n if (!domain) {\n throw new Error(`平台模式需要基础域名,请设置环境变量 ${envName}`);\n }\n return domain;\n}\n\ntype RequestInterceptorManager = {\n use: (\n onFulfilled?: RequestInterceptor['onFulfilled'],\n onRejected?: RequestInterceptor['onRejected'],\n ) => number;\n eject: (id: number) => void;\n};\n\ninterface InterceptorHost {\n request: RequestInterceptorManager;\n}\n\nexport function registerPlatformPlugin(\n interceptors: InterceptorHost,\n options: PlatformPluginOptions,\n): JWTTokenManager | undefined {\n if (!options.enabled) {\n return undefined;\n }\n\n ensureNoAccessKeyOverride('platform.defaultClaims', options.defaultClaims);\n\n // 提前检查 AK/SK,避免每次请求时检查\n const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;\n const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;\n\n const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || '');\n // if (!accessKey) {\n // throw new Error(`平台鉴权需要 access key,请设置环境变量 ${accessKeyEnv}`);\n // }\n\n const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n // if (!secretKey) {\n // throw new Error(`平台鉴权需要 secret key,请设置环境变量 ${secretKeyEnv}`);\n // }\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // 创建 JWT Token Manager(支持缓存和自动刷新)\n const tokenManager = new JWTTokenManager({\n accessKey,\n secretKey,\n expireTimeMs,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n\n const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;\n\n // 注册请求拦截器\n interceptors.request.use((config: RequestConfig) => {\n ensureNoAccessKeyOverride('request.platformAuth.customClaims', config.platformAuth?.customClaims);\n\n const claims: CustomClaims = {\n ...(options.defaultClaims || {}),\n ...(config.platformAuth?.customClaims || {}),\n access_key: accessKey,\n };\n\n // 使用 token manager 获取缓存的 token\n const token = tokenManager.getToken(claims);\n\n // 统一使用 Record<string, string> 类型,由 client.ts 的 normalizeHeaders 处理\n const headers: Record<string, string> = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${token}`,\n 'x-api-key': accessKey,\n };\n\n // 管理端 innerapi:运行时读取用户凭证并注入\n if (options.adminInnerApi) {\n const clientToken = process.env[clientTokenEnv];\n if (clientToken) {\n headers[MIAODA_CLIENT_TOKEN_HEADER] = clientToken;\n }\n }\n\n return {\n ...config,\n headers,\n };\n });\n\n // 返回 token manager 供外部使用(如手动清理缓存)\n return tokenManager;\n}\n\nfunction ensureNoAccessKeyOverride(name: string, claims?: PlatformClaims) {\n if (claims && Object.prototype.hasOwnProperty.call(claims, 'access_key')) {\n throw new Error(`${name} 不允许设置 access_key`);\n }\n}\n","/**\n * 拦截器管理器(模仿 Axios 的 InterceptorManager)\n */\nexport class InterceptorManager<T> {\n private interceptors: (T | null)[] = [];\n\n /**\n * 添加拦截器\n * @returns 拦截器 ID,用于后续移除\n */\n use(onFulfilled?: any, onRejected?: any): number {\n this.interceptors.push({ onFulfilled, onRejected } as T);\n return this.interceptors.length - 1;\n }\n\n /**\n * 移除拦截器\n */\n eject(id: number): void {\n if (this.interceptors[id]) {\n this.interceptors[id] = null;\n }\n }\n\n /**\n * 清空所有拦截器\n */\n clear(): void {\n this.interceptors = [];\n }\n\n /**\n * 遍历所有拦截器\n */\n forEach(fn: (interceptor: T) => void): void {\n this.interceptors.forEach((interceptor) => {\n if (interceptor !== null) {\n fn(interceptor);\n }\n });\n }\n}\n","/**\n * Checks if a value is a plain object.\n * @param value The value to check.\n * @returns True if the value is a plain object, false otherwise.\n */\nexport function isPlainObject(value: any): boolean {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nexport function normalizeHeaders(headers?: RequestInit['headers']): Record<string, string> {\n if (!headers) {\n return {};\n }\n\n if (headers instanceof Headers) {\n const normalized: Record<string, string> = {};\n headers.forEach((value, key) => {\n normalized[key] = value;\n });\n return normalized;\n }\n\n if (Array.isArray(headers)) {\n return headers.reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = value;\n return acc;\n }, {});\n }\n\n const normalized: Record<string, string> = {};\n Object.entries(headers as Record<string, string | readonly string[]>).forEach(\n ([key, value]) => {\n const normalizedValue = Array.isArray(value) ? value.join(',') : value;\n normalized[key] = normalizedValue as string;\n },\n );\n return normalized;\n}\n","import type { PlatformPluginOptions, PlatformRequestAuthConfig } from '../plugins/platform-plugin';\nimport { normalizeHeaders } from './utils';\n\n/**\n * 清理后的请求配置(移除敏感信息)\n */\nexport interface SafeRequestConfig extends Omit<RequestConfig, 'headers'> {\n headers?: Record<string, string>; // 已清理的 headers\n}\n\n/**\n * HTTP 错误对象\n *\n * 注意:为了安全考虑,error.config 中的敏感 headers (如 Authorization, x-api-key)\n * 会被替换为 '[REDACTED]'\n */\nexport class HttpError extends Error {\n public readonly isHttpError: boolean = true;\n public response: Response | undefined;\n public config: SafeRequestConfig;\n\n constructor(\n response: Response | undefined,\n config: RequestConfig,\n message?: string,\n ) {\n super(message || `Request failed with status ${response?.status || 'unknown'}`);\n this.name = 'HttpError';\n this.response = response;\n this.config = sanitizeConfig(config);\n Object.setPrototypeOf(this, HttpError.prototype);\n }\n}\n\nexport interface RequestConfig extends RequestInit {\n url?: string;\n params?: Record<string, any>;\n timeout?: number;\n baseURL?: string;\n platformAuth?: PlatformRequestAuthConfig;\n}\n\nexport interface SecurityConfig {\n /**\n * 允许的 URL 协议白名单\n * 默认: null (不限制)\n * 设置为 ['http:', 'https:'] 可限制只允许 HTTP/HTTPS\n */\n allowedProtocols?: string[] | null;\n\n /**\n * 最大响应体大小(字节)\n * 默认: 0 (不限制)\n * 设置为正数可限制响应体大小,防止内存耗尽攻击\n */\n maxResponseSize?: number;\n\n /**\n * 是否启用严格模式(启用所有安全检查)\n * 默认: false\n * 启用后会自动设置:\n * - allowedProtocols = ['http:', 'https:']\n * - maxResponseSize = 50MB\n */\n strictMode?: boolean;\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n}\n\n/**\n * 请求拦截器函数\n */\nexport type RequestInterceptor = {\n onFulfilled?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 响应拦截器函数(fetch 风格)\n */\nexport type ResponseInterceptor = {\n onFulfilled?: (response: Response) => Response | Promise<Response>;\n onRejected?: (error: any) => any;\n};\n\n/**\n * 清理配置中的敏感信息\n */\nfunction sanitizeConfig(config: RequestConfig): SafeRequestConfig {\n const { headers, ...rest } = config;\n\n return {\n ...rest,\n headers: sanitizeHeaders(headers),\n };\n}\n\n/**\n * 清理敏感 headers\n *\n * 将 Authorization, x-api-key, cookie 等敏感字段替换为 '[REDACTED]'\n */\nfunction sanitizeHeaders(headers?: RequestInit['headers']): Record<string, string> | undefined {\n if (!headers) {\n return undefined;\n }\n\n const normalized = normalizeHeaders(headers);\n const sanitized: Record<string, string> = {};\n\n const sensitiveHeaders = ['authorization', 'x-api-key', 'cookie', 'x-secret'];\n\n for (const [key, value] of Object.entries(normalized)) {\n const lowerKey = key.toLowerCase();\n if (sensitiveHeaders.includes(lowerKey)) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = value;\n }\n }\n\n return sanitized;\n}\n","import { registerPlatformPlugin, resolvePlatformBaseURL } from '../plugins/platform-plugin';\nimport { InterceptorManager } from './InterceptorManager';\nimport {\n HttpError,\n type HttpClientConfig,\n type RequestConfig,\n type RequestInterceptor,\n type ResponseInterceptor,\n type SecurityConfig,\n} from './types';\nimport { isPlainObject, normalizeHeaders } from './utils';\n\n/**\n * 技术栈无关的 HTTP 客户端\n *\n * 设计理念:\n * - 基于原生 fetch API,零依赖\n * - 拦截器模式遵循 Axios 标准,AI 模型和开发者都熟悉\n * - 可在任何 Node.js 环境使用(NestJS, Express, Koa, 纯 Node)\n *\n * @example\n * ```typescript\n * const client = new HttpClient({\n * baseURL: 'https://api.example.com',\n * timeout: 5000,\n * });\n *\n * // 添加请求拦截器(类似 Axios)\n * client.interceptors.request.use(\n * config => {\n * config.headers = config.headers || {};\n * config.headers['Authorization'] = 'Bearer token';\n * return config;\n * },\n * error => Promise.reject(error)\n * );\n *\n * // 添加响应拦截器(类似 Axios)\n * client.interceptors.response.use(\n * response => {\n * console.log('Response:', response.status);\n * return response;\n * },\n * error => {\n * if (error.response?.status === 401) {\n * // 处理未授权\n * }\n * return Promise.reject(error);\n * }\n * );\n *\n * // 发起请求\n * const response = await client.get('/users');\n * console.log(response.data);\n * ```\n */\nexport class HttpClient {\n private defaultConfig: RequestConfig;\n private securityConfig: Required<SecurityConfig>;\n /**\n * 拦截器管理器(API 与 Axios 一致)\n */\n public interceptors = {\n request: new InterceptorManager<RequestInterceptor>(),\n response: new InterceptorManager<ResponseInterceptor>(),\n };\n\n constructor(config?: HttpClientConfig) {\n const { platform, security, ...restConfig } = config || {};\n\n this.defaultConfig = {\n timeout: 5000,\n ...restConfig,\n };\n\n // 初始化安全配置\n const strictMode = security?.strictMode ?? false;\n this.securityConfig = {\n allowedProtocols: strictMode ? ['http:', 'https:'] : null,\n maxResponseSize: strictMode ? 50 * 1024 * 1024 : 0, // 50MB\n strictMode,\n ...security,\n };\n\n if (platform?.enabled) {\n if (!this.defaultConfig.baseURL) {\n this.defaultConfig.baseURL = resolvePlatformBaseURL(platform);\n }\n registerPlatformPlugin(this.interceptors, platform);\n }\n }\n\n /**\n * GET 请求\n */\n async get(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'GET' });\n }\n\n /**\n * POST 请求\n */\n async post(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'POST',\n body,\n headers,\n });\n }\n\n /**\n * PUT 请求\n */\n async put(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PUT',\n body,\n headers,\n });\n }\n\n /**\n * DELETE 请求\n */\n async delete(url: string, config?: RequestConfig): Promise<Response> {\n return this.request({ ...config, url, method: 'DELETE' });\n }\n\n /**\n * PATCH 请求\n */\n async patch(url: string, data?: any, config?: RequestConfig): Promise<Response> {\n const headers: Record<string, string> = { ...config?.headers } as Record<string, string>;\n let body: any = data;\n\n if (isPlainObject(data)) {\n body = JSON.stringify(data);\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json';\n }\n }\n\n return this.request({\n ...config,\n url,\n method: 'PATCH',\n body,\n headers,\n });\n }\n\n /**\n * 核心请求方法(fetch 风格)\n */\n async request(config: RequestConfig): Promise<Response> {\n // 1. 合并配置\n // Perform a case-insensitive header merge that preserves the final key's casing.\n const baseHeaders = normalizeHeaders(this.defaultConfig.headers);\n const overrideHeaders = normalizeHeaders(config.headers);\n const mergedKeyMap: Record<string, { key: string; value: string }> = {};\n\n for (const key in baseHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: baseHeaders[key] };\n }\n for (const key in overrideHeaders) {\n mergedKeyMap[key.toLowerCase()] = { key, value: overrideHeaders[key] };\n }\n\n const finalHeaders: Record<string, string> = {};\n for (const lowerKey in mergedKeyMap) {\n const { key, value } = mergedKeyMap[lowerKey];\n finalHeaders[key] = value;\n }\n\n let mergedConfig: RequestConfig = {\n ...this.defaultConfig,\n ...config,\n headers: finalHeaders,\n };\n\n // 2. 执行请求拦截器\n try {\n mergedConfig = await this.runRequestInterceptors(mergedConfig);\n } catch (error) {\n return Promise.reject(error);\n }\n\n // 3. 统一 normalize headers(拦截器可能返回 Headers 对象)\n mergedConfig.headers = normalizeHeaders(mergedConfig.headers);\n\n // 4. 构建完整 URL\n const fullUrl = this.buildUrl(mergedConfig.url!, mergedConfig.params);\n\n // 5. 设置超时和取消\n const timeout = mergedConfig.timeout || this.defaultConfig.timeout || 5000;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n // 监听外部 signal\n const externalSignal = mergedConfig.signal;\n if (externalSignal) {\n // 如果外部 signal 已经终止,立即终止\n if (externalSignal.aborted) {\n clearTimeout(timeoutId);\n controller.abort();\n } else {\n // 使用 { once: true } 避免内存泄漏\n externalSignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n }\n\n // 6. 准备 fetch 配置(排除自定义字段)\n const { platformAuth, params, timeout: _timeout, baseURL, url, ...fetchOptions } = mergedConfig;\n\n try {\n // 7. 发起请求\n const response = await fetch(fullUrl, {\n ...fetchOptions,\n signal: controller.signal, // 传递我们自己的 controller signal\n });\n\n clearTimeout(timeoutId);\n\n // 安全检查:响应体大小限制\n if (this.securityConfig.maxResponseSize > 0) {\n const contentLength = response.headers.get('content-length');\n if (contentLength) {\n const size = parseInt(contentLength, 10);\n if (size > this.securityConfig.maxResponseSize) {\n throw new Error(\n `Response size ${size} bytes exceeds limit of ${this.securityConfig.maxResponseSize} bytes`\n );\n }\n }\n }\n\n // 检查响应状态\n if (!response.ok) {\n const error = new HttpError(response, mergedConfig);\n return this.runResponseInterceptors(Promise.reject(error));\n }\n\n // 执行响应拦截器(直接返回 Response)\n return this.runResponseInterceptors(Promise.resolve(response));\n } catch (error) {\n clearTimeout(timeoutId);\n\n // 网络错误或超时错误\n const httpError =\n error instanceof HttpError\n ? error\n : new HttpError(\n undefined,\n mergedConfig,\n (error as Error).name === 'AbortError'\n ? 'Request aborted'\n : (error as Error).message,\n );\n\n return this.runResponseInterceptors(Promise.reject(httpError));\n }\n }\n\n /**\n * 执行请求拦截器链\n */\n private runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {\n let promise: Promise<RequestConfig> = Promise.resolve(config);\n\n const interceptors: RequestInterceptor[] = [];\n this.interceptors.request.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n promise = promise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return promise;\n }\n\n /**\n * 执行响应拦截器链\n */\n private async runResponseInterceptors(\n promise: Promise<Response>,\n ): Promise<Response> {\n let currentPromise = promise;\n\n // 按顺序执行所有响应拦截器\n const interceptors: ResponseInterceptor[] = [];\n this.interceptors.response.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n currentPromise = currentPromise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return currentPromise;\n }\n\n /**\n * 构建完整 URL(包含 baseURL 和 query params)\n */\n private buildUrl(url: string, params?: Record<string, any>): string {\n const baseURL = this.defaultConfig.baseURL;\n\n // 拼接 baseURL 和 url(Axios 风格)\n // - 如果 url 是绝对路径(含协议),直接使用 url\n // - 否则拼接 baseURL 和 url,正确处理斜杠\n let combinedUrl: string;\n if (!baseURL || /^https?:\\/\\//i.test(url)) {\n combinedUrl = url;\n } else {\n combinedUrl = baseURL.replace(/\\/+$/, '') + '/' + url.replace(/^\\/+/, '');\n }\n\n const fullUrl = new URL(combinedUrl);\n\n // 协议白名单检查\n const { allowedProtocols } = this.securityConfig;\n if (allowedProtocols && !allowedProtocols.includes(fullUrl.protocol)) {\n throw new Error(\n `Protocol ${fullUrl.protocol} is not allowed. Allowed: ${allowedProtocols.join(', ')}`\n );\n }\n\n // 合并 query params\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n fullUrl.searchParams.set(key, String(value));\n }\n }\n\n return fullUrl.href;\n }\n}\n"],"mappings":"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","names":["normalized"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/http-client",
3
- "version": "0.1.3-alpha.0",
3
+ "version": "0.1.3-alpha.2",
4
4
  "description": "HTTP client with Axios-style interceptors, built on native fetch API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,10 +28,8 @@
28
28
  "author": "Lark APaaS",
29
29
  "license": "MIT",
30
30
  "devDependencies": {
31
- "@types/jest": "^29.5.0",
32
31
  "@types/node": "^18.0.0",
33
- "jest": "^29.7.0",
34
- "ts-jest": "^29.1.1",
35
- "typescript": "^5.0.0"
32
+ "typescript": "^5.0.0",
33
+ "vitest": "^3.2.4"
36
34
  }
37
35
  }