@lark-apaas/http-client 0.1.3 → 0.1.5-beta.0

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
@@ -15,7 +15,6 @@
15
15
  - ✅ **Query Params**: 自动处理查询参数
16
16
  - ✅ **baseURL 支持**: 支持配置基础 URL
17
17
  - ✅ **平台 JWT 鉴权**: 可选启用 AK/SK → JWT 方案,自动注入 Authorization 与 x-api-key
18
- - ✅ **文件 Token 支持**: 支持从文件读取外部签发的 JWT token,多路径优先级链 + 自动 fallback AK/SK
19
18
  - ✅ **JWT 缓存**: 自动缓存 token,在过期前自动刷新,支持多租户和多用户
20
19
  - ✅ **安全特性**: 敏感信息脱敏、协议白名单、响应体大小限制
21
20
 
@@ -405,57 +404,50 @@ await client.post('/secure-resource', body, {
405
404
  > ⚠️ 默认情况下该能力关闭;若 AK/SK 缺失或 JWT 生成失败,会直接抛错阻断请求。
406
405
  > 注意:平台默认/请求级 `customClaims` 禁止设置 `access_key`,该字段始终由 SDK 根据 AK 自动填充。
407
406
 
408
- ### 文件 Token Provider
407
+ ### 性能优化:Token 缓存
409
408
 
410
- 在某些场景下(如 openclaw),JWT token 由外部进程签发并写入本地文件。通过配置 `tokenProvider` 可让 SDK 自动从文件读取 token,无需 AK/SK:
409
+ 平台插件会自动缓存生成的 JWT token,避免每次请求都重新生成:
411
410
 
412
- ```typescript
413
- // 最简配置:使用内置默认路径
414
- const client = new HttpClient({
415
- platform: {
416
- enabled: true,
417
- tokenProvider: { type: 'file' },
418
- },
419
- });
411
+ - **缓存策略**:基于 `claims` 的组合(支持多租户和多用户场景)
412
+ - **自动刷新**:在 token 过期前 5 分钟自动刷新(可通过 `refreshBeforeMs` 配置)
413
+ - **多租户支持**:不同 `tenant_id` 或 `user_id` 会生成不同的 token 并分别缓存
420
414
 
421
- // 自定义路径
415
+ ```typescript
422
416
  const client = new HttpClient({
423
417
  platform: {
424
418
  enabled: true,
425
- tokenProvider: {
426
- type: 'file',
427
- paths: ['/custom/path/a', '/custom/path/b'],
428
- },
419
+ refreshBeforeMs: 3 * 60 * 1000, // 过期前 3 分钟刷新
429
420
  },
430
421
  });
431
422
  ```
432
423
 
433
- **工作机制:**
434
-
435
- - `paths` 可选,不传时使用内置默认路径
436
- - 按 `paths` 数组顺序依次尝试,命中第一个有效 token 即返回
437
- - `Authorization` 和 `x-api-key` 均从文件中的 JWT payload 解析,无需额外配置
438
- - 基于 token 的 `exp` 字段做内存缓存,在过期前 `refreshBeforeMs` 窗口内重新读取文件
439
- - 所有路径均无有效 token 时,自动 fallback 到 AK/SK 自签逻辑
440
- - 消费方无需感知 token 来源,行为完全透明
424
+ ### 管理端 innerapi(仅用于开发态)
441
425
 
442
- ### 性能优化:Token 缓存
426
+ 妙搭开发态 innerapi 在网关侧配置了 admin 插件,会额外注入用户/Agent 身份。开启 `adminInnerApi` 后 SDK 会:
443
427
 
444
- 平台插件会自动缓存生成的 JWT token,避免每次请求都重新生成:
445
-
446
- - **缓存策略**:基于 `claims` 的组合(支持多租户和多用户场景)
447
- - **自动刷新**:在 token 过期前 5 分钟自动刷新(可通过 `refreshBeforeMs` 配置)
448
- - **多租户支持**:不同 `tenant_id` 或 `user_id` 会生成不同的 token 并分别缓存
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` 只是叠加,不是替换)
449
431
 
450
432
  ```typescript
451
433
  const client = new HttpClient({
452
434
  platform: {
453
435
  enabled: true,
454
- refreshBeforeMs: 3 * 60 * 1000, // 过期前 3 分钟刷新
436
+ adminInnerApi: true, // 开启管理端 innerapi 链路
437
+ // adminDomainEnv: 'MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX', // 默认即此
438
+ // clientTokenEnv: 'MIAODA_AUTHN_CODE', // 默认即此
455
439
  },
456
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}');
457
445
  ```
458
446
 
447
+ > `MIAODA_AUTHN_CODE` 由网关在运行时注入,初始化阶段不存在也不会校验。请求时若读不到,则不注入 `X-Miaoda-Client-Token` 头(其它请求头不受影响)。
448
+ >
449
+ > 管理端 innerapi 仅在妙搭开发态部署,运行态请勿开启 `adminInnerApi`。
450
+
459
451
  ## 安全特性
460
452
 
461
453
  ### 严格模式
@@ -628,6 +620,9 @@ interface HttpClientConfig extends RequestConfig {
628
620
  app_env?: string;
629
621
  sandbox_id?: string;
630
622
  };
623
+ adminInnerApi?: boolean; // 管理端 innerapi 链路(仅开发态)
624
+ adminDomainEnv?: string; // 管理端域名环境变量(默认 MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX)
625
+ clientTokenEnv?: string; // 用户凭证环境变量(默认 MIAODA_AUTHN_CODE)
631
626
  };
632
627
  security?: SecurityConfig; // 安全配置
633
628
  }
@@ -770,12 +765,6 @@ interface PlatformPluginOptions {
770
765
  expireTimeMs?: number; // JWT 过期时间
771
766
  refreshBeforeMs?: number; // Token 刷新时机
772
767
  defaultClaims?: PlatformClaims;
773
- tokenProvider?: TokenProviderConfig; // 文件 Token Provider
774
- }
775
-
776
- interface TokenProviderConfig {
777
- type: 'file'; // Provider 类型
778
- paths?: string[]; // 文件路径优先级链(可选,不传使用内置默认路径)
779
768
  }
780
769
 
781
770
  interface PlatformRequestAuthConfig {
package/dist/index.d.mts CHANGED
@@ -136,10 +136,6 @@ declare class JWTTokenManager {
136
136
  }
137
137
 
138
138
  type PlatformClaims = Omit<CustomClaims, 'access_key'>;
139
- interface TokenProviderConfig {
140
- type: 'file';
141
- paths?: string[];
142
- }
143
139
  interface PlatformPluginOptions {
144
140
  enabled?: boolean;
145
141
  baseURL?: string;
@@ -151,7 +147,17 @@ interface PlatformPluginOptions {
151
147
  expireTimeMs?: number;
152
148
  refreshBeforeMs?: number;
153
149
  defaultClaims?: PlatformClaims;
154
- tokenProvider?: TokenProviderConfig;
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;
155
161
  }
156
162
  interface PlatformRequestAuthConfig {
157
163
  customClaims?: PlatformClaims;
@@ -325,49 +331,4 @@ declare class HttpClient {
325
331
  private buildUrl;
326
332
  }
327
333
 
328
- interface FileTokenProviderConfig {
329
- paths?: string[];
330
- refreshBeforeMs?: number;
331
- }
332
- /**
333
- * 从文件读取 JWT Token 的 Provider
334
- *
335
- * 支持多路径优先级链:按顺序尝试读取,命中第一个有效 token 即返回。
336
- * 缓存策略基于 token 自身的 exp 字段,在过期前 refreshBeforeMs 窗口内重新读取文件。
337
- *
338
- * @example
339
- * ```typescript
340
- * const provider = new FileTokenProvider({
341
- * paths: ['/path/a/token', '/path/b/token'],
342
- * refreshBeforeMs: 5 * 60 * 1000,
343
- * });
344
- *
345
- * const result = provider.getToken();
346
- * if (result) {
347
- * console.log(result.token, result.accessKey);
348
- * }
349
- * ```
350
- */
351
- declare class FileTokenProvider {
352
- private cache;
353
- private paths;
354
- private refreshBeforeMs;
355
- constructor(config?: FileTokenProviderConfig);
356
- /**
357
- * 获取 token,按路径优先级依次尝试
358
- *
359
- * 1. 缓存有效且不在刷新窗口 → 直接返回
360
- * 2. 进入刷新窗口 → 重读文件,更新缓存
361
- * 3. 文件未更新但缓存未过期 → 仍返回旧缓存
362
- * 4. 所有路径均无有效 token → 返回 null
363
- */
364
- getToken(): {
365
- token: string;
366
- accessKey: string;
367
- } | null;
368
- clearCache(): void;
369
- private readAndParse;
370
- private decodePayload;
371
- }
372
-
373
- export { type CustomClaims, DEFAULT_CLOCK_TOLERANCE_SEC, DEFAULT_JWT_EXPIRE_TIME_MS, FileTokenProvider, type FileTokenProviderConfig, HttpClient, type HttpClientConfig, HttpError, type JWTClaims, type JWTConfig, type JWTVerifyOptions, type PlatformClaims, type PlatformPluginOptions, type PlatformRequestAuthConfig, type RequestConfig, type RequestInterceptor, type ResponseInterceptor, type SafeRequestConfig, type SecurityConfig, type TokenProviderConfig, generateJWTToken, parseJWTTokenWithVerify, registerPlatformPlugin, resolvePlatformBaseURL };
334
+ export { type CustomClaims, DEFAULT_CLOCK_TOLERANCE_SEC, DEFAULT_JWT_EXPIRE_TIME_MS, HttpClient, type HttpClientConfig, HttpError, type JWTClaims, type JWTConfig, type JWTVerifyOptions, type PlatformClaims, type PlatformPluginOptions, type PlatformRequestAuthConfig, type RequestConfig, type RequestInterceptor, type ResponseInterceptor, type SafeRequestConfig, type SecurityConfig, generateJWTToken, parseJWTTokenWithVerify, registerPlatformPlugin, resolvePlatformBaseURL };
package/dist/index.d.ts CHANGED
@@ -136,10 +136,6 @@ declare class JWTTokenManager {
136
136
  }
137
137
 
138
138
  type PlatformClaims = Omit<CustomClaims, 'access_key'>;
139
- interface TokenProviderConfig {
140
- type: 'file';
141
- paths?: string[];
142
- }
143
139
  interface PlatformPluginOptions {
144
140
  enabled?: boolean;
145
141
  baseURL?: string;
@@ -151,7 +147,17 @@ interface PlatformPluginOptions {
151
147
  expireTimeMs?: number;
152
148
  refreshBeforeMs?: number;
153
149
  defaultClaims?: PlatformClaims;
154
- tokenProvider?: TokenProviderConfig;
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;
155
161
  }
156
162
  interface PlatformRequestAuthConfig {
157
163
  customClaims?: PlatformClaims;
@@ -325,49 +331,4 @@ declare class HttpClient {
325
331
  private buildUrl;
326
332
  }
327
333
 
328
- interface FileTokenProviderConfig {
329
- paths?: string[];
330
- refreshBeforeMs?: number;
331
- }
332
- /**
333
- * 从文件读取 JWT Token 的 Provider
334
- *
335
- * 支持多路径优先级链:按顺序尝试读取,命中第一个有效 token 即返回。
336
- * 缓存策略基于 token 自身的 exp 字段,在过期前 refreshBeforeMs 窗口内重新读取文件。
337
- *
338
- * @example
339
- * ```typescript
340
- * const provider = new FileTokenProvider({
341
- * paths: ['/path/a/token', '/path/b/token'],
342
- * refreshBeforeMs: 5 * 60 * 1000,
343
- * });
344
- *
345
- * const result = provider.getToken();
346
- * if (result) {
347
- * console.log(result.token, result.accessKey);
348
- * }
349
- * ```
350
- */
351
- declare class FileTokenProvider {
352
- private cache;
353
- private paths;
354
- private refreshBeforeMs;
355
- constructor(config?: FileTokenProviderConfig);
356
- /**
357
- * 获取 token,按路径优先级依次尝试
358
- *
359
- * 1. 缓存有效且不在刷新窗口 → 直接返回
360
- * 2. 进入刷新窗口 → 重读文件,更新缓存
361
- * 3. 文件未更新但缓存未过期 → 仍返回旧缓存
362
- * 4. 所有路径均无有效 token → 返回 null
363
- */
364
- getToken(): {
365
- token: string;
366
- accessKey: string;
367
- } | null;
368
- clearCache(): void;
369
- private readAndParse;
370
- private decodePayload;
371
- }
372
-
373
- export { type CustomClaims, DEFAULT_CLOCK_TOLERANCE_SEC, DEFAULT_JWT_EXPIRE_TIME_MS, FileTokenProvider, type FileTokenProviderConfig, HttpClient, type HttpClientConfig, HttpError, type JWTClaims, type JWTConfig, type JWTVerifyOptions, type PlatformClaims, type PlatformPluginOptions, type PlatformRequestAuthConfig, type RequestConfig, type RequestInterceptor, type ResponseInterceptor, type SafeRequestConfig, type SecurityConfig, type TokenProviderConfig, generateJWTToken, parseJWTTokenWithVerify, registerPlatformPlugin, resolvePlatformBaseURL };
334
+ export { type CustomClaims, DEFAULT_CLOCK_TOLERANCE_SEC, DEFAULT_JWT_EXPIRE_TIME_MS, HttpClient, type HttpClientConfig, HttpError, type JWTClaims, type JWTConfig, type JWTVerifyOptions, type PlatformClaims, type PlatformPluginOptions, type PlatformRequestAuthConfig, type RequestConfig, type RequestInterceptor, type ResponseInterceptor, type SafeRequestConfig, type SecurityConfig, generateJWTToken, parseJWTTokenWithVerify, registerPlatformPlugin, resolvePlatformBaseURL };
package/dist/index.js CHANGED
@@ -22,7 +22,6 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_CLOCK_TOLERANCE_SEC: () => DEFAULT_CLOCK_TOLERANCE_SEC,
24
24
  DEFAULT_JWT_EXPIRE_TIME_MS: () => DEFAULT_JWT_EXPIRE_TIME_MS,
25
- FileTokenProvider: () => FileTokenProvider,
26
25
  HttpClient: () => HttpClient,
27
26
  HttpError: () => HttpError,
28
27
  generateJWTToken: () => generateJWTToken,
@@ -200,83 +199,13 @@ var JWTTokenManager = class {
200
199
  }
201
200
  };
202
201
 
203
- // src/helper/file-token-provider.ts
204
- var import_fs = require("fs");
205
- var DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1e3;
206
- var DEFAULT_TOKEN_PATHS = [
207
- "/home/gem/workspace/.force/openclaw/miaoda-provider-key"
208
- ];
209
- var FileTokenProvider = class {
210
- cache = /* @__PURE__ */ new Map();
211
- paths;
212
- refreshBeforeMs;
213
- constructor(config) {
214
- this.paths = config?.paths ?? DEFAULT_TOKEN_PATHS;
215
- this.refreshBeforeMs = config?.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
216
- }
217
- /**
218
- * 获取 token,按路径优先级依次尝试
219
- *
220
- * 1. 缓存有效且不在刷新窗口 → 直接返回
221
- * 2. 进入刷新窗口 → 重读文件,更新缓存
222
- * 3. 文件未更新但缓存未过期 → 仍返回旧缓存
223
- * 4. 所有路径均无有效 token → 返回 null
224
- */
225
- getToken() {
226
- const now = Date.now();
227
- for (const filePath of this.paths) {
228
- const cached = this.cache.get(filePath);
229
- if (cached && cached.expiresAtMs - now > this.refreshBeforeMs) {
230
- return { token: cached.token, accessKey: cached.accessKey };
231
- }
232
- const result = this.readAndParse(filePath);
233
- if (result) {
234
- this.cache.set(filePath, result);
235
- if (result.expiresAtMs > now) {
236
- return { token: result.token, accessKey: result.accessKey };
237
- }
238
- }
239
- if (cached && cached.expiresAtMs > now) {
240
- return { token: cached.token, accessKey: cached.accessKey };
241
- }
242
- }
243
- return null;
244
- }
245
- clearCache() {
246
- this.cache.clear();
247
- }
248
- readAndParse(filePath) {
249
- try {
250
- const token = (0, import_fs.readFileSync)(filePath, "utf-8").trim();
251
- if (!token) return null;
252
- const payload = this.decodePayload(token);
253
- if (!payload?.exp) return null;
254
- return {
255
- token,
256
- accessKey: payload.access_key ?? "",
257
- expiresAtMs: payload.exp * 1e3
258
- };
259
- } catch {
260
- return null;
261
- }
262
- }
263
- decodePayload(token) {
264
- try {
265
- const segments = token.split(".");
266
- if (segments.length !== 3) return null;
267
- const padLength = (4 - (segments[1].length % 4 || 4)) % 4;
268
- const padded = `${segments[1]}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
269
- return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
270
- } catch {
271
- return null;
272
- }
273
- }
274
- };
275
-
276
202
  // src/plugins/platform-plugin.ts
277
203
  var DEFAULT_DOMAIN_ENV = "FORCE_AUTHN_INNERAPI_DOMAIN";
278
204
  var DEFAULT_ACCESS_KEY_ENV = "FORCE_AUTHN_ACCESS_KEY";
279
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";
280
209
  function resolvePlatformBaseURL(options) {
281
210
  if (!options?.enabled) {
282
211
  return options?.baseURL;
@@ -284,7 +213,7 @@ function resolvePlatformBaseURL(options) {
284
213
  if (options.baseURL) {
285
214
  return options.baseURL;
286
215
  }
287
- const envName = options.domainEnv || DEFAULT_DOMAIN_ENV;
216
+ const envName = options.adminInnerApi ? options.adminDomainEnv || DEFAULT_ADMIN_DOMAIN_ENV : options.domainEnv || DEFAULT_DOMAIN_ENV;
288
217
  const domain = process.env[envName];
289
218
  if (!domain) {
290
219
  throw new Error(`\u5E73\u53F0\u6A21\u5F0F\u9700\u8981\u57FA\u7840\u57DF\u540D\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${envName}`);
@@ -296,13 +225,6 @@ function registerPlatformPlugin(interceptors, options) {
296
225
  return void 0;
297
226
  }
298
227
  ensureNoAccessKeyOverride("platform.defaultClaims", options.defaultClaims);
299
- let fileTokenProvider;
300
- if (options.tokenProvider?.type === "file") {
301
- fileTokenProvider = new FileTokenProvider({
302
- paths: options.tokenProvider.paths,
303
- refreshBeforeMs: options.refreshBeforeMs
304
- });
305
- }
306
228
  const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;
307
229
  const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;
308
230
  const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || "");
@@ -314,18 +236,8 @@ function registerPlatformPlugin(interceptors, options) {
314
236
  expireTimeMs,
315
237
  refreshBeforeMs: options.refreshBeforeMs
316
238
  });
239
+ const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;
317
240
  interceptors.request.use((config) => {
318
- if (fileTokenProvider) {
319
- const fileToken = fileTokenProvider.getToken();
320
- if (fileToken) {
321
- const headers2 = {
322
- ...config.headers,
323
- "Authorization": `Bearer ${fileToken.token}`,
324
- "x-api-key": fileToken.accessKey
325
- };
326
- return { ...config, headers: headers2 };
327
- }
328
- }
329
241
  ensureNoAccessKeyOverride("request.platformAuth.customClaims", config.platformAuth?.customClaims);
330
242
  const claims = {
331
243
  ...options.defaultClaims || {},
@@ -338,6 +250,12 @@ function registerPlatformPlugin(interceptors, options) {
338
250
  "Authorization": `Bearer ${token}`,
339
251
  "x-api-key": accessKey
340
252
  };
253
+ if (options.adminInnerApi) {
254
+ const clientToken = process.env[clientTokenEnv];
255
+ if (clientToken) {
256
+ headers[MIAODA_CLIENT_TOKEN_HEADER] = clientToken;
257
+ }
258
+ }
341
259
  return {
342
260
  ...config,
343
261
  headers
@@ -706,7 +624,6 @@ var HttpClient = class {
706
624
  0 && (module.exports = {
707
625
  DEFAULT_CLOCK_TOLERANCE_SEC,
708
626
  DEFAULT_JWT_EXPIRE_TIME_MS,
709
- FileTokenProvider,
710
627
  HttpClient,
711
628
  HttpError,
712
629
  generateJWTToken,
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/helper/file-token-provider.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 TokenProviderConfig,\n} from './plugins/platform-plugin';\nexport {\n registerPlatformPlugin,\n resolvePlatformBaseURL,\n} from './plugins/platform-plugin';\n\nexport { FileTokenProvider } from './helper/file-token-provider';\nexport type { FileTokenProviderConfig } from './helper/file-token-provider';\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 { readFileSync } from 'fs';\n\ninterface CachedFileToken {\n token: string;\n accessKey: string;\n expiresAtMs: number;\n}\n\nexport interface FileTokenProviderConfig {\n paths?: string[];\n refreshBeforeMs?: number;\n}\n\nconst DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1000;\nconst DEFAULT_TOKEN_PATHS = [\n '/home/gem/workspace/.force/openclaw/miaoda-provider-key',\n];\n\n/**\n * 从文件读取 JWT Token 的 Provider\n *\n * 支持多路径优先级链:按顺序尝试读取,命中第一个有效 token 即返回。\n * 缓存策略基于 token 自身的 exp 字段,在过期前 refreshBeforeMs 窗口内重新读取文件。\n *\n * @example\n * ```typescript\n * const provider = new FileTokenProvider({\n * paths: ['/path/a/token', '/path/b/token'],\n * refreshBeforeMs: 5 * 60 * 1000,\n * });\n *\n * const result = provider.getToken();\n * if (result) {\n * console.log(result.token, result.accessKey);\n * }\n * ```\n */\nexport class FileTokenProvider {\n private cache = new Map<string, CachedFileToken>();\n private paths: string[];\n private refreshBeforeMs: number;\n\n constructor(config?: FileTokenProviderConfig) {\n this.paths = config?.paths ?? DEFAULT_TOKEN_PATHS;\n this.refreshBeforeMs = config?.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;\n }\n\n /**\n * 获取 token,按路径优先级依次尝试\n *\n * 1. 缓存有效且不在刷新窗口 → 直接返回\n * 2. 进入刷新窗口 → 重读文件,更新缓存\n * 3. 文件未更新但缓存未过期 → 仍返回旧缓存\n * 4. 所有路径均无有效 token → 返回 null\n */\n getToken(): { token: string; accessKey: string } | null {\n const now = Date.now();\n\n for (const filePath of this.paths) {\n const cached = this.cache.get(filePath);\n\n // 缓存命中且不在刷新窗口\n if (cached && cached.expiresAtMs - now > this.refreshBeforeMs) {\n return { token: cached.token, accessKey: cached.accessKey };\n }\n\n // 需要读取/重读文件\n const result = this.readAndParse(filePath);\n if (result) {\n this.cache.set(filePath, result);\n if (result.expiresAtMs > now) {\n return { token: result.token, accessKey: result.accessKey };\n }\n }\n\n // 文件未更新,但缓存的 token 仍未过期\n if (cached && cached.expiresAtMs > now) {\n return { token: cached.token, accessKey: cached.accessKey };\n }\n }\n\n return null;\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n private readAndParse(filePath: string): CachedFileToken | null {\n try {\n const token = readFileSync(filePath, 'utf-8').trim();\n if (!token) return null;\n\n const payload = this.decodePayload(token);\n if (!payload?.exp) return null;\n\n return {\n token,\n accessKey: payload.access_key ?? '',\n expiresAtMs: payload.exp * 1000,\n };\n } catch {\n return null;\n }\n }\n\n private decodePayload(token: string): Record<string, any> | null {\n try {\n const segments = token.split('.');\n if (segments.length !== 3) return null;\n\n const padLength = (4 - (segments[1].length % 4 || 4)) % 4;\n const padded = `${segments[1]}${'='.repeat(padLength)}`\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n\n return JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));\n } catch {\n return null;\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';\nimport { FileTokenProvider } from '../helper/file-token-provider';\n\nexport type PlatformClaims = Omit<CustomClaims, 'access_key'>;\n\nexport interface TokenProviderConfig {\n type: 'file';\n paths?: string[];\n}\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;\n defaultClaims?: PlatformClaims;\n tokenProvider?: TokenProviderConfig;\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 // 创建文件 token provider(如果配置了)\n let fileTokenProvider: FileTokenProvider | undefined;\n if (options.tokenProvider?.type === 'file') {\n fileTokenProvider = new FileTokenProvider({\n paths: options.tokenProvider.paths,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n }\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 const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // 创建 JWT Token Manager(支持缓存和自动刷新,同时作为 fallback)\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 // 优先尝试文件 token\n if (fileTokenProvider) {\n const fileToken = fileTokenProvider.getToken();\n if (fileToken) {\n const headers = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${fileToken.token}`,\n 'x-api-key': fileToken.accessKey,\n };\n return { ...config, headers };\n }\n }\n\n // Fallback: AK/SK 自签\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 const token = tokenManager.getToken(claims);\n\n const headers = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${token}`,\n 'x-api-key': accessKey,\n };\n\n return {\n ...config,\n headers,\n };\n });\n\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":["headers","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
@@ -166,83 +166,13 @@ var JWTTokenManager = class {
166
166
  }
167
167
  };
168
168
 
169
- // src/helper/file-token-provider.ts
170
- import { readFileSync } from "fs";
171
- var DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1e3;
172
- var DEFAULT_TOKEN_PATHS = [
173
- "/home/gem/workspace/.force/openclaw/miaoda-provider-key"
174
- ];
175
- var FileTokenProvider = class {
176
- cache = /* @__PURE__ */ new Map();
177
- paths;
178
- refreshBeforeMs;
179
- constructor(config) {
180
- this.paths = config?.paths ?? DEFAULT_TOKEN_PATHS;
181
- this.refreshBeforeMs = config?.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
182
- }
183
- /**
184
- * 获取 token,按路径优先级依次尝试
185
- *
186
- * 1. 缓存有效且不在刷新窗口 → 直接返回
187
- * 2. 进入刷新窗口 → 重读文件,更新缓存
188
- * 3. 文件未更新但缓存未过期 → 仍返回旧缓存
189
- * 4. 所有路径均无有效 token → 返回 null
190
- */
191
- getToken() {
192
- const now = Date.now();
193
- for (const filePath of this.paths) {
194
- const cached = this.cache.get(filePath);
195
- if (cached && cached.expiresAtMs - now > this.refreshBeforeMs) {
196
- return { token: cached.token, accessKey: cached.accessKey };
197
- }
198
- const result = this.readAndParse(filePath);
199
- if (result) {
200
- this.cache.set(filePath, result);
201
- if (result.expiresAtMs > now) {
202
- return { token: result.token, accessKey: result.accessKey };
203
- }
204
- }
205
- if (cached && cached.expiresAtMs > now) {
206
- return { token: cached.token, accessKey: cached.accessKey };
207
- }
208
- }
209
- return null;
210
- }
211
- clearCache() {
212
- this.cache.clear();
213
- }
214
- readAndParse(filePath) {
215
- try {
216
- const token = readFileSync(filePath, "utf-8").trim();
217
- if (!token) return null;
218
- const payload = this.decodePayload(token);
219
- if (!payload?.exp) return null;
220
- return {
221
- token,
222
- accessKey: payload.access_key ?? "",
223
- expiresAtMs: payload.exp * 1e3
224
- };
225
- } catch {
226
- return null;
227
- }
228
- }
229
- decodePayload(token) {
230
- try {
231
- const segments = token.split(".");
232
- if (segments.length !== 3) return null;
233
- const padLength = (4 - (segments[1].length % 4 || 4)) % 4;
234
- const padded = `${segments[1]}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
235
- return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
236
- } catch {
237
- return null;
238
- }
239
- }
240
- };
241
-
242
169
  // src/plugins/platform-plugin.ts
243
170
  var DEFAULT_DOMAIN_ENV = "FORCE_AUTHN_INNERAPI_DOMAIN";
244
171
  var DEFAULT_ACCESS_KEY_ENV = "FORCE_AUTHN_ACCESS_KEY";
245
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";
246
176
  function resolvePlatformBaseURL(options) {
247
177
  if (!options?.enabled) {
248
178
  return options?.baseURL;
@@ -250,7 +180,7 @@ function resolvePlatformBaseURL(options) {
250
180
  if (options.baseURL) {
251
181
  return options.baseURL;
252
182
  }
253
- const envName = options.domainEnv || DEFAULT_DOMAIN_ENV;
183
+ const envName = options.adminInnerApi ? options.adminDomainEnv || DEFAULT_ADMIN_DOMAIN_ENV : options.domainEnv || DEFAULT_DOMAIN_ENV;
254
184
  const domain = process.env[envName];
255
185
  if (!domain) {
256
186
  throw new Error(`\u5E73\u53F0\u6A21\u5F0F\u9700\u8981\u57FA\u7840\u57DF\u540D\uFF0C\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${envName}`);
@@ -262,13 +192,6 @@ function registerPlatformPlugin(interceptors, options) {
262
192
  return void 0;
263
193
  }
264
194
  ensureNoAccessKeyOverride("platform.defaultClaims", options.defaultClaims);
265
- let fileTokenProvider;
266
- if (options.tokenProvider?.type === "file") {
267
- fileTokenProvider = new FileTokenProvider({
268
- paths: options.tokenProvider.paths,
269
- refreshBeforeMs: options.refreshBeforeMs
270
- });
271
- }
272
195
  const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;
273
196
  const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;
274
197
  const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || "");
@@ -280,18 +203,8 @@ function registerPlatformPlugin(interceptors, options) {
280
203
  expireTimeMs,
281
204
  refreshBeforeMs: options.refreshBeforeMs
282
205
  });
206
+ const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;
283
207
  interceptors.request.use((config) => {
284
- if (fileTokenProvider) {
285
- const fileToken = fileTokenProvider.getToken();
286
- if (fileToken) {
287
- const headers2 = {
288
- ...config.headers,
289
- "Authorization": `Bearer ${fileToken.token}`,
290
- "x-api-key": fileToken.accessKey
291
- };
292
- return { ...config, headers: headers2 };
293
- }
294
- }
295
208
  ensureNoAccessKeyOverride("request.platformAuth.customClaims", config.platformAuth?.customClaims);
296
209
  const claims = {
297
210
  ...options.defaultClaims || {},
@@ -304,6 +217,12 @@ function registerPlatformPlugin(interceptors, options) {
304
217
  "Authorization": `Bearer ${token}`,
305
218
  "x-api-key": accessKey
306
219
  };
220
+ if (options.adminInnerApi) {
221
+ const clientToken = process.env[clientTokenEnv];
222
+ if (clientToken) {
223
+ headers[MIAODA_CLIENT_TOKEN_HEADER] = clientToken;
224
+ }
225
+ }
307
226
  return {
308
227
  ...config,
309
228
  headers
@@ -671,7 +590,6 @@ var HttpClient = class {
671
590
  export {
672
591
  DEFAULT_CLOCK_TOLERANCE_SEC,
673
592
  DEFAULT_JWT_EXPIRE_TIME_MS,
674
- FileTokenProvider,
675
593
  HttpClient,
676
594
  HttpError,
677
595
  generateJWTToken,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helper/jwt.ts","../src/helper/jwt-manager.ts","../src/helper/file-token-provider.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 { readFileSync } from 'fs';\n\ninterface CachedFileToken {\n token: string;\n accessKey: string;\n expiresAtMs: number;\n}\n\nexport interface FileTokenProviderConfig {\n paths?: string[];\n refreshBeforeMs?: number;\n}\n\nconst DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1000;\nconst DEFAULT_TOKEN_PATHS = [\n '/home/gem/workspace/.force/openclaw/miaoda-provider-key',\n];\n\n/**\n * 从文件读取 JWT Token 的 Provider\n *\n * 支持多路径优先级链:按顺序尝试读取,命中第一个有效 token 即返回。\n * 缓存策略基于 token 自身的 exp 字段,在过期前 refreshBeforeMs 窗口内重新读取文件。\n *\n * @example\n * ```typescript\n * const provider = new FileTokenProvider({\n * paths: ['/path/a/token', '/path/b/token'],\n * refreshBeforeMs: 5 * 60 * 1000,\n * });\n *\n * const result = provider.getToken();\n * if (result) {\n * console.log(result.token, result.accessKey);\n * }\n * ```\n */\nexport class FileTokenProvider {\n private cache = new Map<string, CachedFileToken>();\n private paths: string[];\n private refreshBeforeMs: number;\n\n constructor(config?: FileTokenProviderConfig) {\n this.paths = config?.paths ?? DEFAULT_TOKEN_PATHS;\n this.refreshBeforeMs = config?.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;\n }\n\n /**\n * 获取 token,按路径优先级依次尝试\n *\n * 1. 缓存有效且不在刷新窗口 → 直接返回\n * 2. 进入刷新窗口 → 重读文件,更新缓存\n * 3. 文件未更新但缓存未过期 → 仍返回旧缓存\n * 4. 所有路径均无有效 token → 返回 null\n */\n getToken(): { token: string; accessKey: string } | null {\n const now = Date.now();\n\n for (const filePath of this.paths) {\n const cached = this.cache.get(filePath);\n\n // 缓存命中且不在刷新窗口\n if (cached && cached.expiresAtMs - now > this.refreshBeforeMs) {\n return { token: cached.token, accessKey: cached.accessKey };\n }\n\n // 需要读取/重读文件\n const result = this.readAndParse(filePath);\n if (result) {\n this.cache.set(filePath, result);\n if (result.expiresAtMs > now) {\n return { token: result.token, accessKey: result.accessKey };\n }\n }\n\n // 文件未更新,但缓存的 token 仍未过期\n if (cached && cached.expiresAtMs > now) {\n return { token: cached.token, accessKey: cached.accessKey };\n }\n }\n\n return null;\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n private readAndParse(filePath: string): CachedFileToken | null {\n try {\n const token = readFileSync(filePath, 'utf-8').trim();\n if (!token) return null;\n\n const payload = this.decodePayload(token);\n if (!payload?.exp) return null;\n\n return {\n token,\n accessKey: payload.access_key ?? '',\n expiresAtMs: payload.exp * 1000,\n };\n } catch {\n return null;\n }\n }\n\n private decodePayload(token: string): Record<string, any> | null {\n try {\n const segments = token.split('.');\n if (segments.length !== 3) return null;\n\n const padLength = (4 - (segments[1].length % 4 || 4)) % 4;\n const padded = `${segments[1]}${'='.repeat(padLength)}`\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n\n return JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));\n } catch {\n return null;\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';\nimport { FileTokenProvider } from '../helper/file-token-provider';\n\nexport type PlatformClaims = Omit<CustomClaims, 'access_key'>;\n\nexport interface TokenProviderConfig {\n type: 'file';\n paths?: string[];\n}\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;\n defaultClaims?: PlatformClaims;\n tokenProvider?: TokenProviderConfig;\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 // 创建文件 token provider(如果配置了)\n let fileTokenProvider: FileTokenProvider | undefined;\n if (options.tokenProvider?.type === 'file') {\n fileTokenProvider = new FileTokenProvider({\n paths: options.tokenProvider.paths,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n }\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 const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // 创建 JWT Token Manager(支持缓存和自动刷新,同时作为 fallback)\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 // 优先尝试文件 token\n if (fileTokenProvider) {\n const fileToken = fileTokenProvider.getToken();\n if (fileToken) {\n const headers = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${fileToken.token}`,\n 'x-api-key': fileToken.accessKey,\n };\n return { ...config, headers };\n }\n }\n\n // Fallback: AK/SK 自签\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 const token = tokenManager.getToken(claims);\n\n const headers = {\n ...(config.headers as Record<string, string>),\n 'Authorization': `Bearer ${token}`,\n 'x-api-key': accessKey,\n };\n\n return {\n ...config,\n headers,\n };\n });\n\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":["headers","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",
3
+ "version": "0.1.5-beta.0",
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",