@lark-apaas/http-client 0.1.7-alpha.1 → 0.1.7-alpha.11
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 +41 -0
- package/dist/index.d.mts +58 -10
- package/dist/index.d.ts +58 -10
- package/dist/index.js +107 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +106 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
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
|
|
18
19
|
- ✅ **JWT 缓存**: 自动缓存 token,在过期前自动刷新,支持多租户和多用户
|
|
19
20
|
- ✅ **安全特性**: 敏感信息脱敏、协议白名单、响应体大小限制
|
|
20
21
|
|
|
@@ -404,6 +405,40 @@ await client.post('/secure-resource', body, {
|
|
|
404
405
|
> ⚠️ 默认情况下该能力关闭;若 AK/SK 缺失或 JWT 生成失败,会直接抛错阻断请求。
|
|
405
406
|
> 注意:平台默认/请求级 `customClaims` 禁止设置 `access_key`,该字段始终由 SDK 根据 AK 自动填充。
|
|
406
407
|
|
|
408
|
+
### 文件 Token Provider
|
|
409
|
+
|
|
410
|
+
在某些场景下(如 openclaw),JWT token 由外部进程签发并写入本地文件。通过配置 `tokenProvider` 可让 SDK 自动从文件读取 token,无需 AK/SK:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// 最简配置:使用内置默认路径
|
|
414
|
+
const client = new HttpClient({
|
|
415
|
+
platform: {
|
|
416
|
+
enabled: true,
|
|
417
|
+
tokenProvider: { type: 'file' },
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// 自定义路径
|
|
422
|
+
const client = new HttpClient({
|
|
423
|
+
platform: {
|
|
424
|
+
enabled: true,
|
|
425
|
+
tokenProvider: {
|
|
426
|
+
type: 'file',
|
|
427
|
+
paths: ['/custom/path/a', '/custom/path/b'],
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
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 来源,行为完全透明
|
|
441
|
+
|
|
407
442
|
### 性能优化:Token 缓存
|
|
408
443
|
|
|
409
444
|
平台插件会自动缓存生成的 JWT token,避免每次请求都重新生成:
|
|
@@ -765,6 +800,12 @@ interface PlatformPluginOptions {
|
|
|
765
800
|
expireTimeMs?: number; // JWT 过期时间
|
|
766
801
|
refreshBeforeMs?: number; // Token 刷新时机
|
|
767
802
|
defaultClaims?: PlatformClaims;
|
|
803
|
+
tokenProvider?: TokenProviderConfig; // 文件 Token Provider
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
interface TokenProviderConfig {
|
|
807
|
+
type: 'file'; // Provider 类型
|
|
808
|
+
paths?: string[]; // 文件路径优先级链(可选,不传使用内置默认路径)
|
|
768
809
|
}
|
|
769
810
|
|
|
770
811
|
interface PlatformRequestAuthConfig {
|
package/dist/index.d.mts
CHANGED
|
@@ -136,6 +136,10 @@ 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
|
+
}
|
|
139
143
|
interface PlatformPluginOptions {
|
|
140
144
|
enabled?: boolean;
|
|
141
145
|
baseURL?: string;
|
|
@@ -148,16 +152,14 @@ interface PlatformPluginOptions {
|
|
|
148
152
|
refreshBeforeMs?: number;
|
|
149
153
|
defaultClaims?: PlatformClaims;
|
|
150
154
|
/**
|
|
151
|
-
* 平台直接派发的静态
|
|
152
|
-
* -
|
|
153
|
-
* - 若同时配置了 sk
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* 优先级:`jwt` option > `process.env[jwtEnv ?? 'FORCE_AUTHN_JWT']`
|
|
157
|
-
* 都没配置时:完全退到 sk 自签(向后兼容旧行为)
|
|
155
|
+
* 平台直接派发的静态 token(短期凭据,默认 5 天有效,当前实现为 JWT)。配置后:
|
|
156
|
+
* - 优先用此 token 调 InnerAPI(不再用 sk 自签),消除 sk 长期落地的泄漏面
|
|
157
|
+
* - 若同时配置了 sk(accessKey/secretKey 或对应 env),sk 自签作为最终 fallback
|
|
158
|
+
* 优先级:`token` option > `process.env[tokenEnv ?? 'FORCE_AUTHN_TOKEN']`
|
|
159
|
+
* 与 `tokenProvider`(文件 token)共存:fileToken > envToken > sk 自签。
|
|
158
160
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
token?: string;
|
|
162
|
+
tokenEnv?: string;
|
|
161
163
|
/**
|
|
162
164
|
* 管理端 innerapi 链路(仅用于妙搭开发态)
|
|
163
165
|
* 启用后:
|
|
@@ -169,6 +171,7 @@ interface PlatformPluginOptions {
|
|
|
169
171
|
adminInnerApi?: boolean;
|
|
170
172
|
adminDomainEnv?: string;
|
|
171
173
|
clientTokenEnv?: string;
|
|
174
|
+
tokenProvider?: TokenProviderConfig;
|
|
172
175
|
}
|
|
173
176
|
interface PlatformRequestAuthConfig {
|
|
174
177
|
customClaims?: PlatformClaims;
|
|
@@ -355,4 +358,49 @@ declare class HttpClient {
|
|
|
355
358
|
private buildUrl;
|
|
356
359
|
}
|
|
357
360
|
|
|
358
|
-
|
|
361
|
+
interface FileTokenProviderConfig {
|
|
362
|
+
paths?: string[];
|
|
363
|
+
refreshBeforeMs?: number;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* 从文件读取 JWT Token 的 Provider
|
|
367
|
+
*
|
|
368
|
+
* 支持多路径优先级链:按顺序尝试读取,命中第一个有效 token 即返回。
|
|
369
|
+
* 缓存策略基于 token 自身的 exp 字段,在过期前 refreshBeforeMs 窗口内重新读取文件。
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```typescript
|
|
373
|
+
* const provider = new FileTokenProvider({
|
|
374
|
+
* paths: ['/path/a/token', '/path/b/token'],
|
|
375
|
+
* refreshBeforeMs: 5 * 60 * 1000,
|
|
376
|
+
* });
|
|
377
|
+
*
|
|
378
|
+
* const result = provider.getToken();
|
|
379
|
+
* if (result) {
|
|
380
|
+
* console.log(result.token, result.accessKey);
|
|
381
|
+
* }
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
declare class FileTokenProvider {
|
|
385
|
+
private cache;
|
|
386
|
+
private paths;
|
|
387
|
+
private refreshBeforeMs;
|
|
388
|
+
constructor(config?: FileTokenProviderConfig);
|
|
389
|
+
/**
|
|
390
|
+
* 获取 token,按路径优先级依次尝试
|
|
391
|
+
*
|
|
392
|
+
* 1. 缓存有效且不在刷新窗口 → 直接返回
|
|
393
|
+
* 2. 进入刷新窗口 → 重读文件,更新缓存
|
|
394
|
+
* 3. 文件未更新但缓存未过期 → 仍返回旧缓存
|
|
395
|
+
* 4. 所有路径均无有效 token → 返回 null
|
|
396
|
+
*/
|
|
397
|
+
getToken(): {
|
|
398
|
+
token: string;
|
|
399
|
+
accessKey: string;
|
|
400
|
+
} | null;
|
|
401
|
+
clearCache(): void;
|
|
402
|
+
private readAndParse;
|
|
403
|
+
private decodePayload;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export { type CustomClaims, DEFAULT_CLOCK_TOLERANCE_SEC, DEFAULT_JWT_EXPIRE_TIME_MS, FileTokenProvider, type FileTokenProviderConfig, HttpClient, type HttpClientConfig, type HttpClientPreInterceptors, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -136,6 +136,10 @@ 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
|
+
}
|
|
139
143
|
interface PlatformPluginOptions {
|
|
140
144
|
enabled?: boolean;
|
|
141
145
|
baseURL?: string;
|
|
@@ -148,16 +152,14 @@ interface PlatformPluginOptions {
|
|
|
148
152
|
refreshBeforeMs?: number;
|
|
149
153
|
defaultClaims?: PlatformClaims;
|
|
150
154
|
/**
|
|
151
|
-
* 平台直接派发的静态
|
|
152
|
-
* -
|
|
153
|
-
* - 若同时配置了 sk
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* 优先级:`jwt` option > `process.env[jwtEnv ?? 'FORCE_AUTHN_JWT']`
|
|
157
|
-
* 都没配置时:完全退到 sk 自签(向后兼容旧行为)
|
|
155
|
+
* 平台直接派发的静态 token(短期凭据,默认 5 天有效,当前实现为 JWT)。配置后:
|
|
156
|
+
* - 优先用此 token 调 InnerAPI(不再用 sk 自签),消除 sk 长期落地的泄漏面
|
|
157
|
+
* - 若同时配置了 sk(accessKey/secretKey 或对应 env),sk 自签作为最终 fallback
|
|
158
|
+
* 优先级:`token` option > `process.env[tokenEnv ?? 'FORCE_AUTHN_TOKEN']`
|
|
159
|
+
* 与 `tokenProvider`(文件 token)共存:fileToken > envToken > sk 自签。
|
|
158
160
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
token?: string;
|
|
162
|
+
tokenEnv?: string;
|
|
161
163
|
/**
|
|
162
164
|
* 管理端 innerapi 链路(仅用于妙搭开发态)
|
|
163
165
|
* 启用后:
|
|
@@ -169,6 +171,7 @@ interface PlatformPluginOptions {
|
|
|
169
171
|
adminInnerApi?: boolean;
|
|
170
172
|
adminDomainEnv?: string;
|
|
171
173
|
clientTokenEnv?: string;
|
|
174
|
+
tokenProvider?: TokenProviderConfig;
|
|
172
175
|
}
|
|
173
176
|
interface PlatformRequestAuthConfig {
|
|
174
177
|
customClaims?: PlatformClaims;
|
|
@@ -355,4 +358,49 @@ declare class HttpClient {
|
|
|
355
358
|
private buildUrl;
|
|
356
359
|
}
|
|
357
360
|
|
|
358
|
-
|
|
361
|
+
interface FileTokenProviderConfig {
|
|
362
|
+
paths?: string[];
|
|
363
|
+
refreshBeforeMs?: number;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* 从文件读取 JWT Token 的 Provider
|
|
367
|
+
*
|
|
368
|
+
* 支持多路径优先级链:按顺序尝试读取,命中第一个有效 token 即返回。
|
|
369
|
+
* 缓存策略基于 token 自身的 exp 字段,在过期前 refreshBeforeMs 窗口内重新读取文件。
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```typescript
|
|
373
|
+
* const provider = new FileTokenProvider({
|
|
374
|
+
* paths: ['/path/a/token', '/path/b/token'],
|
|
375
|
+
* refreshBeforeMs: 5 * 60 * 1000,
|
|
376
|
+
* });
|
|
377
|
+
*
|
|
378
|
+
* const result = provider.getToken();
|
|
379
|
+
* if (result) {
|
|
380
|
+
* console.log(result.token, result.accessKey);
|
|
381
|
+
* }
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
declare class FileTokenProvider {
|
|
385
|
+
private cache;
|
|
386
|
+
private paths;
|
|
387
|
+
private refreshBeforeMs;
|
|
388
|
+
constructor(config?: FileTokenProviderConfig);
|
|
389
|
+
/**
|
|
390
|
+
* 获取 token,按路径优先级依次尝试
|
|
391
|
+
*
|
|
392
|
+
* 1. 缓存有效且不在刷新窗口 → 直接返回
|
|
393
|
+
* 2. 进入刷新窗口 → 重读文件,更新缓存
|
|
394
|
+
* 3. 文件未更新但缓存未过期 → 仍返回旧缓存
|
|
395
|
+
* 4. 所有路径均无有效 token → 返回 null
|
|
396
|
+
*/
|
|
397
|
+
getToken(): {
|
|
398
|
+
token: string;
|
|
399
|
+
accessKey: string;
|
|
400
|
+
} | null;
|
|
401
|
+
clearCache(): void;
|
|
402
|
+
private readAndParse;
|
|
403
|
+
private decodePayload;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export { type CustomClaims, DEFAULT_CLOCK_TOLERANCE_SEC, DEFAULT_JWT_EXPIRE_TIME_MS, FileTokenProvider, type FileTokenProviderConfig, HttpClient, type HttpClientConfig, type HttpClientPreInterceptors, 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 };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ 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,
|
|
25
26
|
HttpClient: () => HttpClient,
|
|
26
27
|
HttpError: () => HttpError,
|
|
27
28
|
generateJWTToken: () => generateJWTToken,
|
|
@@ -198,11 +199,11 @@ var JWTTokenManager = class {
|
|
|
198
199
|
};
|
|
199
200
|
}
|
|
200
201
|
};
|
|
201
|
-
var
|
|
202
|
+
var StaticTokenSource = class {
|
|
202
203
|
constructor(token) {
|
|
203
204
|
this.token = token;
|
|
204
205
|
if (!token) {
|
|
205
|
-
throw new Error("
|
|
206
|
+
throw new Error("StaticTokenSource: token \u4E0D\u80FD\u4E3A\u7A7A");
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
getToken(_claims) {
|
|
@@ -210,11 +211,84 @@ var StaticJWTSource = class {
|
|
|
210
211
|
}
|
|
211
212
|
};
|
|
212
213
|
|
|
214
|
+
// src/helper/file-token-provider.ts
|
|
215
|
+
var import_fs = require("fs");
|
|
216
|
+
var DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1e3;
|
|
217
|
+
var DEFAULT_TOKEN_PATHS = [
|
|
218
|
+
"/home/gem/workspace/.force/openclaw/miaoda-provider-key"
|
|
219
|
+
];
|
|
220
|
+
var FileTokenProvider = class {
|
|
221
|
+
cache = /* @__PURE__ */ new Map();
|
|
222
|
+
paths;
|
|
223
|
+
refreshBeforeMs;
|
|
224
|
+
constructor(config) {
|
|
225
|
+
this.paths = config?.paths ?? DEFAULT_TOKEN_PATHS;
|
|
226
|
+
this.refreshBeforeMs = config?.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 获取 token,按路径优先级依次尝试
|
|
230
|
+
*
|
|
231
|
+
* 1. 缓存有效且不在刷新窗口 → 直接返回
|
|
232
|
+
* 2. 进入刷新窗口 → 重读文件,更新缓存
|
|
233
|
+
* 3. 文件未更新但缓存未过期 → 仍返回旧缓存
|
|
234
|
+
* 4. 所有路径均无有效 token → 返回 null
|
|
235
|
+
*/
|
|
236
|
+
getToken() {
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
for (const filePath of this.paths) {
|
|
239
|
+
const cached = this.cache.get(filePath);
|
|
240
|
+
if (cached && cached.expiresAtMs - now > this.refreshBeforeMs) {
|
|
241
|
+
return { token: cached.token, accessKey: cached.accessKey };
|
|
242
|
+
}
|
|
243
|
+
const result = this.readAndParse(filePath);
|
|
244
|
+
if (result) {
|
|
245
|
+
this.cache.set(filePath, result);
|
|
246
|
+
if (result.expiresAtMs > now) {
|
|
247
|
+
return { token: result.token, accessKey: result.accessKey };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (cached && cached.expiresAtMs > now) {
|
|
251
|
+
return { token: cached.token, accessKey: cached.accessKey };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
clearCache() {
|
|
257
|
+
this.cache.clear();
|
|
258
|
+
}
|
|
259
|
+
readAndParse(filePath) {
|
|
260
|
+
try {
|
|
261
|
+
const token = (0, import_fs.readFileSync)(filePath, "utf-8").trim();
|
|
262
|
+
if (!token) return null;
|
|
263
|
+
const payload = this.decodePayload(token);
|
|
264
|
+
if (!payload?.exp) return null;
|
|
265
|
+
return {
|
|
266
|
+
token,
|
|
267
|
+
accessKey: payload.access_key ?? "",
|
|
268
|
+
expiresAtMs: payload.exp * 1e3
|
|
269
|
+
};
|
|
270
|
+
} catch {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
decodePayload(token) {
|
|
275
|
+
try {
|
|
276
|
+
const segments = token.split(".");
|
|
277
|
+
if (segments.length !== 3) return null;
|
|
278
|
+
const padLength = (4 - (segments[1].length % 4 || 4)) % 4;
|
|
279
|
+
const padded = `${segments[1]}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
|
|
280
|
+
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
213
287
|
// src/plugins/platform-plugin.ts
|
|
214
288
|
var DEFAULT_DOMAIN_ENV = "FORCE_AUTHN_INNERAPI_DOMAIN";
|
|
215
289
|
var DEFAULT_ACCESS_KEY_ENV = "FORCE_AUTHN_ACCESS_KEY";
|
|
216
290
|
var DEFAULT_SECRET_KEY_ENV = "FORCE_AUTHN_ACCESS_SECRET";
|
|
217
|
-
var
|
|
291
|
+
var DEFAULT_TOKEN_ENV = "FORCE_AUTHN_TOKEN";
|
|
218
292
|
var DEFAULT_ADMIN_DOMAIN_ENV = "MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX";
|
|
219
293
|
var DEFAULT_CLIENT_TOKEN_ENV = "MIAODA_AUTHN_CODE";
|
|
220
294
|
var MIAODA_CLIENT_TOKEN_HEADER = "X-Miaoda-Client-Token";
|
|
@@ -237,14 +311,16 @@ function registerPlatformPlugin(interceptors, options) {
|
|
|
237
311
|
return void 0;
|
|
238
312
|
}
|
|
239
313
|
ensureNoAccessKeyOverride("platform.defaultClaims", options.defaultClaims);
|
|
314
|
+
let fileTokenProvider;
|
|
315
|
+
if (options.tokenProvider?.type === "file") {
|
|
316
|
+
fileTokenProvider = new FileTokenProvider({
|
|
317
|
+
paths: options.tokenProvider.paths,
|
|
318
|
+
refreshBeforeMs: options.refreshBeforeMs
|
|
319
|
+
});
|
|
320
|
+
}
|
|
240
321
|
const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;
|
|
241
322
|
const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;
|
|
242
323
|
const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || "");
|
|
243
|
-
if (!accessKey) {
|
|
244
|
-
throw new Error(
|
|
245
|
-
`\u5E73\u53F0\u9274\u6743\u9700\u8981 access key,\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${accessKeyEnv} \u6216\u4F20 options.accessKey`
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
324
|
const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || "");
|
|
249
325
|
const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;
|
|
250
326
|
const tokenManager = new JWTTokenManager({
|
|
@@ -253,18 +329,22 @@ function registerPlatformPlugin(interceptors, options) {
|
|
|
253
329
|
expireTimeMs,
|
|
254
330
|
refreshBeforeMs: options.refreshBeforeMs
|
|
255
331
|
});
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
if (!staticJwtSource && !secretKey) {
|
|
260
|
-
throw new Error(
|
|
261
|
-
`\u5E73\u53F0\u9274\u6743\u9700\u8981 jwt \u6216 secret key,\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${jwtEnv}(\u63A8\u8350) \u6216 ${secretKeyEnv}`
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
const primaryTokenSource = staticJwtSource ?? tokenManager;
|
|
265
|
-
const fallbackTokenSource = staticJwtSource && secretKey ? tokenManager : null;
|
|
332
|
+
const tokenEnv = options.tokenEnv || DEFAULT_TOKEN_ENV;
|
|
333
|
+
const platformToken = options.token ?? (process.env[tokenEnv] || "");
|
|
334
|
+
const staticTokenSource = platformToken ? new StaticTokenSource(platformToken) : null;
|
|
266
335
|
const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;
|
|
267
336
|
interceptors.request.use((config) => {
|
|
337
|
+
if (fileTokenProvider) {
|
|
338
|
+
const fileToken = fileTokenProvider.getToken();
|
|
339
|
+
if (fileToken) {
|
|
340
|
+
const headers2 = {
|
|
341
|
+
...config.headers,
|
|
342
|
+
"Authorization": `Bearer ${fileToken.token}`,
|
|
343
|
+
"x-api-key": fileToken.accessKey
|
|
344
|
+
};
|
|
345
|
+
return { ...config, headers: headers2 };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
268
348
|
ensureNoAccessKeyOverride("request.platformAuth.customClaims", config.platformAuth?.customClaims);
|
|
269
349
|
const claims = {
|
|
270
350
|
...options.defaultClaims || {},
|
|
@@ -272,11 +352,14 @@ function registerPlatformPlugin(interceptors, options) {
|
|
|
272
352
|
access_key: accessKey
|
|
273
353
|
};
|
|
274
354
|
let token;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
355
|
+
if (staticTokenSource) {
|
|
356
|
+
try {
|
|
357
|
+
token = staticTokenSource.getToken(claims);
|
|
358
|
+
} catch {
|
|
359
|
+
token = tokenManager.getToken(claims);
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
token = tokenManager.getToken(claims);
|
|
280
363
|
}
|
|
281
364
|
const headers = {
|
|
282
365
|
...config.headers,
|
|
@@ -687,6 +770,7 @@ var HttpClient = class {
|
|
|
687
770
|
0 && (module.exports = {
|
|
688
771
|
DEFAULT_CLOCK_TOLERANCE_SEC,
|
|
689
772
|
DEFAULT_JWT_EXPIRE_TIME_MS,
|
|
773
|
+
FileTokenProvider,
|
|
690
774
|
HttpClient,
|
|
691
775
|
HttpError,
|
|
692
776
|
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/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 HttpClientPreInterceptors,\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?: string;\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\n/**\n * Token 来源的最小抽象。`platform-plugin` 用它做 primary / fallback 链\n * (env jwt 优先,sk 自签兜底)。`JWTTokenManager`(下方)和 `StaticJWTSource`\n * (本文件底部)都实现这个接口。\n */\nexport interface TokenSource {\n /**\n * 拿一个可用的 JWT。拿不到必须抛 —— 调用方据此决定走 fallback。\n * 不要返回空串 / null,会让上层 `if (token)` 误判通过。\n */\n getToken(claims: CustomClaims): string;\n}\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 || '',\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\n/**\n * 平台直接派发的静态 JWT(短期凭据,默认 5 天有效),client 不签也不刷,\n * 拿到啥就用啥。过期了 server 端会 401,由 platform-plugin 的 fallback 链\n * 退到 sk 自签(JWTTokenManager)。\n *\n * 用于 §2.2 InnerAPI 鉴权 — ak/sk → jwt + ak 演进:\n * `.env.local` 仅注入 jwt(不再下发 sk),消除 sk 长期落地的泄漏面。\n */\nexport class StaticJWTSource implements TokenSource {\n constructor(private readonly token: string) {\n if (!token) {\n throw new Error('StaticJWTSource: token 不能为空');\n }\n }\n\n getToken(_claims: CustomClaims): string {\n return this.token;\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, StaticJWTSource, TokenSource } 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 * 平台直接派发的静态 JWT(短期凭据,默认 5 天有效)。配置后:\n * - 默认使用此 jwt 调 InnerAPI(不再用 sk 自签),消除 sk 长期落地的泄漏面\n * - 若同时配置了 sk(`accessKey`/`secretKey`/对应 env),作为 fallback 自动启用:\n * * 启动期没拿到 jwt → 退到 sk 自签\n * * (TODO) 运行期 jwt 401 → 退到 sk 自签(本期未做,需 response interceptor)\n * 优先级:`jwt` option > `process.env[jwtEnv ?? 'FORCE_AUTHN_JWT']`\n * 都没配置时:完全退到 sk 自签(向后兼容旧行为)\n */\n jwt?: string;\n jwtEnv?: string;\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_JWT_ENV = 'FORCE_AUTHN_JWT';\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 // ak 必填:不论 token 来源是 envJwt 还是 sk 自签,server 端都靠 x-api-key 路由到对应账号\n // 后再验 jwt 签名,缺 ak 会 silent 退化为 x-api-key:'',真实失败发生在 server 端 401\n // 难追根因。在启动期 fail-fast。\n if (!accessKey) {\n throw new Error(\n `平台鉴权需要 access key,请设置环境变量 ${accessKeyEnv} 或传 options.accessKey`,\n );\n }\n\n // sk 在 jwt 模式下可以缺(envJwt 提供 Bearer);只在两者都缺时才完全无凭证可用。\n // sk 与 envJwt 至少要有一个:由下面 primaryTokenSource 解析时统一判断。\n const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // sk 自签 manager(用作 fallback;旧链路保持唯一来源时也是它)\n // 注:即使 sk 为空也照常实例化 —— 保持跟 SDK 历史行为一致(空 key 签的 JWT\n // 会被 server 端拒,但不在 client 这层抛错,留给上层处理)\n const tokenManager = new JWTTokenManager({\n accessKey,\n secretKey,\n expireTimeMs,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n\n // 平台派发的静态 jwt(优先;§2.2 演进的目标)\n const jwtEnv = options.jwtEnv || DEFAULT_JWT_ENV;\n const platformJwt = options.jwt ?? (process.env[jwtEnv] || '');\n const staticJwtSource = platformJwt ? new StaticJWTSource(platformJwt) : null;\n\n // jwt 与 sk 至少要有一个,否则没有任何凭证可签 Authorization,所有 InnerAPI 必 401\n if (!staticJwtSource && !secretKey) {\n throw new Error(\n `平台鉴权需要 jwt 或 secret key,请设置环境变量 ${jwtEnv}(推荐) 或 ${secretKeyEnv}`,\n );\n }\n\n // primary / fallback 链:有 envJwt 用 envJwt,有 sk 用 sk 兜底\n // 上面已保证 staticJwtSource 或 secretKey 至少一个,所以 primaryTokenSource 永远有效\n const primaryTokenSource: TokenSource = staticJwtSource ?? tokenManager;\n const fallbackTokenSource: TokenSource | null =\n staticJwtSource && secretKey ? tokenManager : null;\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 // 默认走 primary(envJwt 或 sk 自签);primary 抛错才退 fallback。\n // 注:StaticJWTSource 不会抛(token 启动期已 non-empty 校验过);\n // JWTTokenManager 也不会抛 —— 这两个在\"正常配置下\"都能拿到 token。\n // fallback 主要给将来的 401 重试用(本期未做,留 hook)。\n let token: string;\n try {\n token = primaryTokenSource.getToken(claims);\n } catch (err) {\n if (!fallbackTokenSource) throw err;\n token = fallbackTokenSource.getToken(claims);\n }\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 // 返回 sk 自签 manager 供外部使用(如手动清理缓存)。\n // 注:envJwt 模式下 tokenManager 仍然存在(作为 fallback),返回它兼容旧调用方;\n // 静态 jwt 不需要缓存管理,所以不暴露 StaticJWTSource。\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 HttpClientPreInterceptors {\n request?: RequestInterceptor[];\n response?: ResponseInterceptor[];\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n preInterceptors?: HttpClientPreInterceptors;\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, preInterceptors, ...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 this.registerRequestPreInterceptors(preInterceptors?.request);\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 this.registerResponsePreInterceptors(preInterceptors?.response);\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 * 注册请求 preInterceptors\n */\n private registerRequestPreInterceptors(interceptors?: RequestInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.request.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n }\n\n /**\n * 注册响应 preInterceptors\n */\n private registerResponsePreInterceptors(interceptors?: ResponseInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.response.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n }\n\n /**\n * 执行请求拦截器链\n */\n private runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {\n let promise: Promise<RequestConfig> = Promise.resolve(config);\n\n const interceptors: RequestInterceptor[] = [];\n this.interceptors.request.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n promise = promise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return promise;\n }\n\n /**\n * 执行响应拦截器链\n */\n private async runResponseInterceptors(\n promise: Promise<Response>,\n ): Promise<Response> {\n let currentPromise = promise;\n\n // 按顺序执行所有响应拦截器\n const interceptors: ResponseInterceptor[] = [];\n this.interceptors.response.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n currentPromise = currentPromise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return currentPromise;\n }\n\n /**\n * 构建完整 URL(包含 baseURL 和 query params)\n */\n private buildUrl(url: string, params?: Record<string, any>): string {\n const baseURL = this.defaultConfig.baseURL;\n\n // 拼接 baseURL 和 url(Axios 风格)\n // - 如果 url 是绝对路径(含协议),直接使用 url\n // - 否则拼接 baseURL 和 url,正确处理斜杠\n let combinedUrl: string;\n if (!baseURL || /^https?:\\/\\//i.test(url)) {\n combinedUrl = url;\n } else {\n combinedUrl = baseURL.replace(/\\/+$/, '') + '/' + url.replace(/^\\/+/, '');\n }\n\n const fullUrl = new URL(combinedUrl);\n\n // 协议白名单检查\n const { allowedProtocols } = this.securityConfig;\n if (allowedProtocols && !allowedProtocols.includes(fullUrl.protocol)) {\n throw new Error(\n `Protocol ${fullUrl.protocol} is not allowed. Allowed: ${allowedProtocols.join(', ')}`\n );\n }\n\n // 合并 query params\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n fullUrl.searchParams.set(key, String(value));\n }\n }\n\n return fullUrl.href;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAAwD;AAwCjD,IAAM,6BAA6B,KAAK,KAAK;AAC7C,IAAM,8BAA8B;AAE3C,IAAM,aAAa;AAAA,EACjB,KAAK;AAAA,EACL,KAAK;AACP;AAEO,SAAS,iBACd,cACA,QACQ;AACR,QAAM,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC/C,QAAM,UAAqB;AAAA,IACzB,GAAG;AAAA,IACH,KAAK,aAAa;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,aAAa,KAAK,MAAM,OAAO,eAAe,GAAI;AAAA,IACvD,SAAK,0BAAW;AAAA,EAClB;AAEA,QAAM,gBAAgB,gBAAgB,KAAK,UAAU,UAAU,CAAC;AAChE,QAAM,iBAAiB,gBAAgB,KAAK,UAAU,OAAO,CAAC;AAC9D,QAAM,YAAY,KAAK,GAAG,aAAa,IAAI,cAAc,IAAI,OAAO,SAAS;AAE7E,SAAO,GAAG,aAAa,IAAI,cAAc,IAAI,SAAS;AACxD;AAEO,SAAS,wBACd,aACA,QACA,SACW;AACX,QAAM,WAAW,YAAY,MAAM,GAAG;AACtC,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,QAAM,CAAC,eAAe,gBAAgB,SAAS,IAAI;AAGnD,QAAM,aAAa,KAAK,MAAM,gBAAgB,aAAa,CAAC;AAC5D,MAAI,WAAW,QAAQ,SAAS;AAC9B,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAGA,QAAM,oBAAoB,KAAK,GAAG,aAAa,IAAI,cAAc,IAAI,OAAO,SAAS;AAErF,QAAM,iBAAiB,OAAO,KAAK,iBAAiB;AACpD,QAAM,kBAAkB,OAAO,KAAK,SAAS;AAC7C,MACE,eAAe,WAAW,gBAAgB,UAC1C,KAAC,+BAAgB,gBAAgB,eAAe,GAChD;AACA,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,UAAU,KAAK,MAAM,gBAAgB,cAAc,CAAC;AAG1D,MAAI,CAAC,SAAS,gBAAgB;AAC5B,UAAM,iBAAiB,SAAS,kBAAkB;AAClD,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAGxC,QAAI,QAAQ,QAAQ,QAAW;AAC7B,UAAI,QAAQ,MAAM,iBAAiB,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,wBAAwB,IAAI,KAAK,QAAQ,MAAM,GAAI,EAAE,YAAY,CAAC;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,QAAQ,QAAW;AAC7B,UAAI,QAAQ,MAAM,iBAAiB,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,6CAA6C,IAAI,KAAK,QAAQ,MAAM,GAAI,EAAE,YAAY,CAAC;AAAA,QACzF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,QAAQ,QAAW;AAC7B,UAAI,QAAQ,MAAM,iBAAiB,KAAK;AACtC,cAAM,IAAI;AAAA,UACR,qCAAqC,IAAI,KAAK,QAAQ,MAAM,GAAI,EAAE,YAAY,CAAC;AAAA,QACjF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,KAAK,OAAe,QAAwB;AACnD,aAAO,0BAAW,UAAU,MAAM,EAC/B,OAAO,KAAK,EACZ,OAAO,QAAQ,EACf,QAAQ,MAAM,EAAE,EAChB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AACvB;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,OAAO,KAAK,KAAK,EACrB,SAAS,QAAQ,EACjB,QAAQ,MAAM,EAAE,EAChB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG;AACvB;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,aAAa,KAAK,MAAM,SAAS,KAAK,MAAM;AAClD,QAAM,SAAS,GAAG,KAAK,GAAG,IAAI,OAAO,SAAS,CAAC,GAC5C,QAAQ,MAAM,GAAG,EACjB,QAAQ,MAAM,GAAG;AACpB,SAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,MAAM;AACtD;;;AC7GO,IAAM,kBAAN,MAAsB;AAAA,EACnB,QAAQ,oBAAI,IAAyB;AAAA,EACrC;AAAA,EAER,YAAY,QAA0B;AACpC,SAAK,SAAS;AAAA,MACZ,iBAAiB,IAAI,KAAK;AAAA;AAAA,MAC1B,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAS,QAA8B;AACrC,UAAM,WAAW,KAAK,YAAY,MAAM;AACxC,UAAM,SAAS,KAAK,MAAM,IAAI,QAAQ;AACtC,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,UAAU,OAAO,cAAc,MAAM,KAAK,OAAO,iBAAiB;AACpE,aAAO,OAAO;AAAA,IAChB;AAGA,UAAM,QAAQ,iBAAiB,QAAQ,KAAK,MAAM;AAClD,UAAM,cAAc,MAAM,KAAK,OAAO;AAEtC,SAAK,MAAM,IAAI,UAAU,EAAE,OAAO,YAAY,CAAC;AAE/C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,QAA4B;AACxC,UAAM,WAAW,KAAK,YAAY,MAAM;AACxC,SAAK,MAAM,OAAO,QAAQ;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,YAAY,QAA8B;AAEhD,UAAM,QAAQ;AAAA,MACZ,OAAO;AAAA,MACP,OAAO,aAAa;AAAA,MACpB,OAAO,WAAW;AAAA,MAClB,OAAO,UAAU;AAAA,MACjB,OAAO,WAAW;AAAA,MAClB,OAAO,cAAc;AAAA,IACvB;AACA,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB;AACd,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,aAAa;AACjB,QAAI,eAAe;AAEnB,eAAW,UAAU,KAAK,MAAM,OAAO,GAAG;AACxC,UAAI,OAAO,cAAc,KAAK;AAC5B;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO,KAAK,MAAM;AAAA,MAClB,OAAO;AAAA,MACP,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAUO,IAAM,kBAAN,MAA6C;AAAA,EAClD,YAA6B,OAAe;AAAf;AAC3B,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,iDAA6B;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,SAAS,SAA+B;AACtC,WAAO,KAAK;AAAA,EACd;AACF;;;AC5HA,IAAM,qBAAqB;AAC3B,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB;AAC/B,IAAM,kBAAkB;AACxB,IAAM,2BAA2B;AACjC,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AAE5B,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,SAAS,SAAS;AACrB,WAAO,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS;AACnB,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,UAAU,QAAQ,gBACnB,QAAQ,kBAAkB,2BAC1B,QAAQ,aAAa;AAC1B,QAAM,SAAS,QAAQ,IAAI,OAAO;AAClC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,gHAAsB,OAAO,EAAE;AAAA,EACjD;AACA,SAAO;AACT;AAcO,SAAS,uBACd,cACA,SAC6B;AAC7B,MAAI,CAAC,QAAQ,SAAS;AACpB,WAAO;AAAA,EACT;AAEA,4BAA0B,0BAA0B,QAAQ,aAAa;AAGzE,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAM,YAAY,QAAQ,cAAc,QAAQ,IAAI,YAAY,KAAK;AAIrE,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR,8FAA6B,YAAY;AAAA,IAC3C;AAAA,EACF;AAIA,QAAM,YAAY,QAAQ,cAAc,QAAQ,IAAI,YAAY,KAAK;AAErE,QAAM,eAAe,QAAQ,gBAAgB;AAK7C,QAAM,eAAe,IAAI,gBAAgB;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB,QAAQ;AAAA,EAC3B,CAAC;AAGD,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,cAAc,QAAQ,QAAQ,QAAQ,IAAI,MAAM,KAAK;AAC3D,QAAM,kBAAkB,cAAc,IAAI,gBAAgB,WAAW,IAAI;AAGzE,MAAI,CAAC,mBAAmB,CAAC,WAAW;AAClC,UAAM,IAAI;AAAA,MACR,yGAAmC,MAAM,yBAAU,YAAY;AAAA,IACjE;AAAA,EACF;AAIA,QAAM,qBAAkC,mBAAmB;AAC3D,QAAM,sBACJ,mBAAmB,YAAY,eAAe;AAEhD,QAAM,iBAAiB,QAAQ,kBAAkB;AAGjD,eAAa,QAAQ,IAAI,CAAC,WAA0B;AAClD,8BAA0B,qCAAqC,OAAO,cAAc,YAAY;AAEhG,UAAM,SAAuB;AAAA,MAC3B,GAAI,QAAQ,iBAAiB,CAAC;AAAA,MAC9B,GAAI,OAAO,cAAc,gBAAgB,CAAC;AAAA,MAC1C,YAAY;AAAA,IACd;AAMA,QAAI;AACJ,QAAI;AACF,cAAQ,mBAAmB,SAAS,MAAM;AAAA,IAC5C,SAAS,KAAK;AACZ,UAAI,CAAC,oBAAqB,OAAM;AAChC,cAAQ,oBAAoB,SAAS,MAAM;AAAA,IAC7C;AAGA,UAAM,UAAkC;AAAA,MACtC,GAAI,OAAO;AAAA,MACX,iBAAiB,UAAU,KAAK;AAAA,MAChC,aAAa;AAAA,IACf;AAGA,QAAI,QAAQ,eAAe;AACzB,YAAM,cAAc,QAAQ,IAAI,cAAc;AAC9C,UAAI,aAAa;AACf,gBAAQ,0BAA0B,IAAI;AAAA,MACxC;AAAA,IACF;AAEA,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAKD,SAAO;AACT;AAEA,SAAS,0BAA0B,MAAc,QAAyB;AACxE,MAAI,UAAU,OAAO,UAAU,eAAe,KAAK,QAAQ,YAAY,GAAG;AACxE,UAAM,IAAI,MAAM,GAAG,IAAI,4CAAmB;AAAA,EAC5C;AACF;;;ACrMO,IAAM,qBAAN,MAA4B;AAAA,EACzB,eAA6B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,IAAI,aAAmB,YAA0B;AAC/C,SAAK,aAAa,KAAK,EAAE,aAAa,WAAW,CAAM;AACvD,WAAO,KAAK,aAAa,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAkB;AACtB,QAAI,KAAK,aAAa,EAAE,GAAG;AACzB,WAAK,aAAa,EAAE,IAAI;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,eAAe,CAAC;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,IAAoC;AAC1C,SAAK,aAAa,QAAQ,CAAC,gBAAgB;AACzC,UAAI,gBAAgB,MAAM;AACxB,WAAG,WAAW;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACpCO,SAAS,cAAc,OAAqB;AACjD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,OAAO,eAAe,KAAK;AACzC,SAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEO,SAAS,iBAAiB,SAA0D;AACzF,MAAI,CAAC,SAAS;AACZ,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,mBAAmB,SAAS;AAC9B,UAAMA,cAAqC,CAAC;AAC5C,YAAQ,QAAQ,CAAC,OAAO,QAAQ;AAC9B,MAAAA,YAAW,GAAG,IAAI;AAAA,IACpB,CAAC;AACD,WAAOA;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QAAQ,OAA+B,CAAC,KAAK,CAAC,KAAK,KAAK,MAAM;AACnE,UAAI,GAAG,IAAI;AACX,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AAEA,QAAM,aAAqC,CAAC;AAC5C,SAAO,QAAQ,OAAqD,EAAE;AAAA,IACpE,CAAC,CAAC,KAAK,KAAK,MAAM;AAChB,YAAM,kBAAkB,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;AACjE,iBAAW,GAAG,IAAI;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;;;ACzBO,IAAM,YAAN,MAAM,mBAAkB,MAAM;AAAA,EACnB,cAAuB;AAAA,EAChC;AAAA,EACA;AAAA,EAEP,YACE,UACA,QACA,SACA;AACA,UAAM,WAAW,8BAA8B,UAAU,UAAU,SAAS,EAAE;AAC9E,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,SAAS,eAAe,MAAM;AACnC,WAAO,eAAe,MAAM,WAAU,SAAS;AAAA,EACjD;AACF;AAiEA,SAAS,eAAe,QAA0C;AAChE,QAAM,EAAE,SAAS,GAAG,KAAK,IAAI;AAE7B,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,gBAAgB,OAAO;AAAA,EAClC;AACF;AAOA,SAAS,gBAAgB,SAAsE;AAC7F,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,iBAAiB,OAAO;AAC3C,QAAM,YAAoC,CAAC;AAE3C,QAAM,mBAAmB,CAAC,iBAAiB,aAAa,UAAU,UAAU;AAE5E,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,UAAM,WAAW,IAAI,YAAY;AACjC,QAAI,iBAAiB,SAAS,QAAQ,GAAG;AACvC,gBAAU,GAAG,IAAI;AAAA,IACnB,OAAO;AACL,gBAAU,GAAG,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;;;AC3EO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAID,eAAe;AAAA,IACpB,SAAS,IAAI,mBAAuC;AAAA,IACpD,UAAU,IAAI,mBAAwC;AAAA,EACxD;AAAA,EAEA,YAAY,QAA2B;AACrC,UAAM,EAAE,UAAU,UAAU,iBAAiB,GAAG,WAAW,IAAI,UAAU,CAAC;AAE1E,SAAK,gBAAgB;AAAA,MACnB,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAGA,UAAM,aAAa,UAAU,cAAc;AAC3C,SAAK,iBAAiB;AAAA,MACpB,kBAAkB,aAAa,CAAC,SAAS,QAAQ,IAAI;AAAA,MACrD,iBAAiB,aAAa,KAAK,OAAO,OAAO;AAAA;AAAA,MACjD;AAAA,MACA,GAAG;AAAA,IACL;AAEA,SAAK,+BAA+B,iBAAiB,OAAO;AAE5D,QAAI,UAAU,SAAS;AACrB,UAAI,CAAC,KAAK,cAAc,SAAS;AAC/B,aAAK,cAAc,UAAU,uBAAuB,QAAQ;AAAA,MAC9D;AACA,6BAAuB,KAAK,cAAc,QAAQ;AAAA,IACpD;AAEA,SAAK,gCAAgC,iBAAiB,QAAQ;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,KAAa,QAA2C;AAChE,WAAO,KAAK,QAAQ,EAAE,GAAG,QAAQ,KAAK,QAAQ,MAAM,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,KAAa,MAAY,QAA2C;AAC7E,UAAM,UAAkC,EAAE,GAAG,QAAQ,QAAQ;AAC7D,QAAI,OAAY;AAEhB,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO,KAAK,UAAU,IAAI;AAC1B,UAAI,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,cAAc,GAAG;AAC7E,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ;AAAA,MAClB,GAAG;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,KAAa,MAAY,QAA2C;AAC5E,UAAM,UAAkC,EAAE,GAAG,QAAQ,QAAQ;AAC7D,QAAI,OAAY;AAEhB,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO,KAAK,UAAU,IAAI;AAC1B,UAAI,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,cAAc,GAAG;AAC7E,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ;AAAA,MAClB,GAAG;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,KAAa,QAA2C;AACnE,WAAO,KAAK,QAAQ,EAAE,GAAG,QAAQ,KAAK,QAAQ,SAAS,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,KAAa,MAAY,QAA2C;AAC9E,UAAM,UAAkC,EAAE,GAAG,QAAQ,QAAQ;AAC7D,QAAI,OAAY;AAEhB,QAAI,cAAc,IAAI,GAAG;AACvB,aAAO,KAAK,UAAU,IAAI;AAC1B,UAAI,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,cAAc,GAAG;AAC7E,gBAAQ,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ;AAAA,MAClB,GAAG;AAAA,MACH;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,QAA0C;AAGtD,UAAM,cAAc,iBAAiB,KAAK,cAAc,OAAO;AAC/D,UAAM,kBAAkB,iBAAiB,OAAO,OAAO;AACvD,UAAM,eAA+D,CAAC;AAEtE,eAAW,OAAO,aAAa;AAC7B,mBAAa,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,OAAO,YAAY,GAAG,EAAE;AAAA,IACnE;AACA,eAAW,OAAO,iBAAiB;AACjC,mBAAa,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,OAAO,gBAAgB,GAAG,EAAE;AAAA,IACvE;AAEA,UAAM,eAAuC,CAAC;AAC9C,eAAW,YAAY,cAAc;AACnC,YAAM,EAAE,KAAK,MAAM,IAAI,aAAa,QAAQ;AAC5C,mBAAa,GAAG,IAAI;AAAA,IACtB;AAEA,QAAI,eAA8B;AAAA,MAChC,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,SAAS;AAAA,IACX;AAGA,QAAI;AACF,qBAAe,MAAM,KAAK,uBAAuB,YAAY;AAAA,IAC/D,SAAS,OAAO;AACd,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B;AAGA,iBAAa,UAAU,iBAAiB,aAAa,OAAO;AAG5D,UAAM,UAAU,KAAK,SAAS,aAAa,KAAM,aAAa,MAAM;AAGpE,UAAM,UAAU,aAAa,WAAW,KAAK,cAAc,WAAW;AACtE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAG9D,UAAM,iBAAiB,aAAa;AACpC,QAAI,gBAAgB;AAElB,UAAI,eAAe,SAAS;AAC1B,qBAAa,SAAS;AACtB,mBAAW,MAAM;AAAA,MACnB,OAAO;AAEL,uBAAe,iBAAiB,SAAS,MAAM,WAAW,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,MACnF;AAAA,IACF;AAGA,UAAM,EAAE,cAAc,QAAQ,SAAS,UAAU,SAAS,KAAK,GAAG,aAAa,IAAI;AAEnF,QAAI;AAEF,YAAM,WAAW,MAAM,MAAM,SAAS;AAAA,QACpC,GAAG;AAAA,QACH,QAAQ,WAAW;AAAA;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAGtB,UAAI,KAAK,eAAe,kBAAkB,GAAG;AAC3C,cAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,YAAI,eAAe;AACjB,gBAAM,OAAO,SAAS,eAAe,EAAE;AACvC,cAAI,OAAO,KAAK,eAAe,iBAAiB;AAC9C,kBAAM,IAAI;AAAA,cACR,iBAAiB,IAAI,2BAA2B,KAAK,eAAe,eAAe;AAAA,YACrF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,QAAQ,IAAI,UAAU,UAAU,YAAY;AAClD,eAAO,KAAK,wBAAwB,QAAQ,OAAO,KAAK,CAAC;AAAA,MAC3D;AAGA,aAAO,KAAK,wBAAwB,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IAC/D,SAAS,OAAO;AACd,mBAAa,SAAS;AAGtB,YAAM,YACJ,iBAAiB,YACb,QACA,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACC,MAAgB,SAAS,eACtB,oBACC,MAAgB;AAAA,MACvB;AAEJ,aAAO,KAAK,wBAAwB,QAAQ,OAAO,SAAS,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,+BAA+B,cAAqC;AAC1E,QAAI,CAAC,cAAc,QAAQ;AACzB;AAAA,IACF;AAEA,eAAW,eAAe,cAAc;AACtC,WAAK,aAAa,QAAQ;AAAA,QACxB,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,gCAAgC,cAAsC;AAC5E,QAAI,CAAC,cAAc,QAAQ;AACzB;AAAA,IACF;AAEA,eAAW,eAAe,cAAc;AACtC,WAAK,aAAa,SAAS;AAAA,QACzB,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB,QAA+C;AAC5E,QAAI,UAAkC,QAAQ,QAAQ,MAAM;AAE5D,UAAM,eAAqC,CAAC;AAC5C,SAAK,aAAa,QAAQ,QAAQ,CAAC,gBAAgB;AACjD,mBAAa,KAAK,WAAW;AAAA,IAC/B,CAAC;AAED,eAAW,eAAe,cAAc;AACtC,gBAAU,QAAQ;AAAA,QAChB,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,wBACZ,SACmB;AACnB,QAAI,iBAAiB;AAGrB,UAAM,eAAsC,CAAC;AAC7C,SAAK,aAAa,SAAS,QAAQ,CAAC,gBAAgB;AAClD,mBAAa,KAAK,WAAW;AAAA,IAC/B,CAAC;AAED,eAAW,eAAe,cAAc;AACtC,uBAAiB,eAAe;AAAA,QAC9B,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,KAAa,QAAsC;AAClE,UAAM,UAAU,KAAK,cAAc;AAKnC,QAAI;AACJ,QAAI,CAAC,WAAW,gBAAgB,KAAK,GAAG,GAAG;AACzC,oBAAc;AAAA,IAChB,OAAO;AACL,oBAAc,QAAQ,QAAQ,QAAQ,EAAE,IAAI,MAAM,IAAI,QAAQ,QAAQ,EAAE;AAAA,IAC1E;AAEA,UAAM,UAAU,IAAI,IAAI,WAAW;AAGnC,UAAM,EAAE,iBAAiB,IAAI,KAAK;AAClC,QAAI,oBAAoB,CAAC,iBAAiB,SAAS,QAAQ,QAAQ,GAAG;AACpE,YAAM,IAAI;AAAA,QACR,YAAY,QAAQ,QAAQ,6BAA6B,iBAAiB,KAAK,IAAI,CAAC;AAAA,MACtF;AAAA,IACF;AAGA,QAAI,QAAQ;AACV,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAI,UAAU,UAAa,UAAU,KAAM;AAC3C,gBAAQ,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MAC7C;AAAA,IACF;AAEA,WAAO,QAAQ;AAAA,EACjB;AACF;","names":["normalized"]}
|
|
1
|
+
{"version":3,"sources":["../src/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 HttpClientPreInterceptors,\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?: string;\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\n/**\n * Token 来源的最小抽象。`platform-plugin` 用它做 primary / fallback 链\n * (env token 优先,sk 自签兜底)。`JWTTokenManager`(下方)和 `StaticTokenSource`\n * (本文件底部)都实现这个接口。\n */\nexport interface TokenSource {\n /**\n * 拿一个可用的 token(用作 Authorization Bearer)。拿不到必须抛 —— 调用方据此决定走 fallback。\n * 不要返回空串 / null,会让上层 `if (token)` 误判通过。\n */\n getToken(claims: CustomClaims): string;\n}\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 || '',\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\n/**\n * 平台直接派发的静态 token(短期凭据,默认 5 天有效,当前实现为 JWT 但对调用方不透明),\n * client 不签也不刷,拿到啥就用啥。过期了 server 端会 401,由 platform-plugin 的 fallback 链\n * 退到 sk 自签(JWTTokenManager)。\n *\n * 用于 §2.2 InnerAPI 鉴权 — ak/sk → token + ak 演进:\n * `.env.local` 仅注入 token(不再下发 sk),消除 sk 长期落地的泄漏面。\n */\nexport class StaticTokenSource implements TokenSource {\n constructor(private readonly token: string) {\n if (!token) {\n throw new Error('StaticTokenSource: token 不能为空');\n }\n }\n\n getToken(_claims: CustomClaims): string {\n return this.token;\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, StaticTokenSource } 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 /**\n * 平台直接派发的静态 token(短期凭据,默认 5 天有效,当前实现为 JWT)。配置后:\n * - 优先用此 token 调 InnerAPI(不再用 sk 自签),消除 sk 长期落地的泄漏面\n * - 若同时配置了 sk(accessKey/secretKey 或对应 env),sk 自签作为最终 fallback\n * 优先级:`token` option > `process.env[tokenEnv ?? 'FORCE_AUTHN_TOKEN']`\n * 与 `tokenProvider`(文件 token)共存:fileToken > envToken > sk 自签。\n */\n token?: string;\n tokenEnv?: string;\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 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';\nconst DEFAULT_TOKEN_ENV = 'FORCE_AUTHN_TOKEN';\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 // 创建文件 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 // 平台派发的静态 token(可选;有则在 fileToken 之后、sk 自签之前用,消除 sk 长期落地的泄漏面)\n const tokenEnv = options.tokenEnv || DEFAULT_TOKEN_ENV;\n const platformToken = options.token ?? (process.env[tokenEnv] || '');\n const staticTokenSource = platformToken ? new StaticTokenSource(platformToken) : null;\n\n const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;\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 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 // 次选 envToken(FORCE_AUTHN_TOKEN env / options.token)—— 短期凭据,跟 fileToken 同样\n // 替代 sk 长期落地。envToken 拿到才用,拿不到继续往下退到 sk 自签。\n // 注:StaticTokenSource.getToken 当前实现是直接返回构造时传入的 token,不会抛 —— 即便将来\n // 改成可能抛的实现(比如带过期校验),也走 sk 自签 fallback。\n let token: string;\n if (staticTokenSource) {\n try {\n token = staticTokenSource.getToken(claims);\n } catch {\n token = tokenManager.getToken(claims);\n }\n } else {\n token = tokenManager.getToken(claims);\n }\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 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 HttpClientPreInterceptors {\n request?: RequestInterceptor[];\n response?: ResponseInterceptor[];\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n preInterceptors?: HttpClientPreInterceptors;\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, preInterceptors, ...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 this.registerRequestPreInterceptors(preInterceptors?.request);\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 this.registerResponsePreInterceptors(preInterceptors?.response);\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 * 注册请求 preInterceptors\n */\n private registerRequestPreInterceptors(interceptors?: RequestInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.request.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n }\n\n /**\n * 注册响应 preInterceptors\n */\n private registerResponsePreInterceptors(interceptors?: ResponseInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.response.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -165,11 +165,11 @@ var JWTTokenManager = class {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
|
-
var
|
|
168
|
+
var StaticTokenSource = class {
|
|
169
169
|
constructor(token) {
|
|
170
170
|
this.token = token;
|
|
171
171
|
if (!token) {
|
|
172
|
-
throw new Error("
|
|
172
|
+
throw new Error("StaticTokenSource: token \u4E0D\u80FD\u4E3A\u7A7A");
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
getToken(_claims) {
|
|
@@ -177,11 +177,84 @@ var StaticJWTSource = class {
|
|
|
177
177
|
}
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
+
// src/helper/file-token-provider.ts
|
|
181
|
+
import { readFileSync } from "fs";
|
|
182
|
+
var DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1e3;
|
|
183
|
+
var DEFAULT_TOKEN_PATHS = [
|
|
184
|
+
"/home/gem/workspace/.force/openclaw/miaoda-provider-key"
|
|
185
|
+
];
|
|
186
|
+
var FileTokenProvider = class {
|
|
187
|
+
cache = /* @__PURE__ */ new Map();
|
|
188
|
+
paths;
|
|
189
|
+
refreshBeforeMs;
|
|
190
|
+
constructor(config) {
|
|
191
|
+
this.paths = config?.paths ?? DEFAULT_TOKEN_PATHS;
|
|
192
|
+
this.refreshBeforeMs = config?.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 获取 token,按路径优先级依次尝试
|
|
196
|
+
*
|
|
197
|
+
* 1. 缓存有效且不在刷新窗口 → 直接返回
|
|
198
|
+
* 2. 进入刷新窗口 → 重读文件,更新缓存
|
|
199
|
+
* 3. 文件未更新但缓存未过期 → 仍返回旧缓存
|
|
200
|
+
* 4. 所有路径均无有效 token → 返回 null
|
|
201
|
+
*/
|
|
202
|
+
getToken() {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
for (const filePath of this.paths) {
|
|
205
|
+
const cached = this.cache.get(filePath);
|
|
206
|
+
if (cached && cached.expiresAtMs - now > this.refreshBeforeMs) {
|
|
207
|
+
return { token: cached.token, accessKey: cached.accessKey };
|
|
208
|
+
}
|
|
209
|
+
const result = this.readAndParse(filePath);
|
|
210
|
+
if (result) {
|
|
211
|
+
this.cache.set(filePath, result);
|
|
212
|
+
if (result.expiresAtMs > now) {
|
|
213
|
+
return { token: result.token, accessKey: result.accessKey };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (cached && cached.expiresAtMs > now) {
|
|
217
|
+
return { token: cached.token, accessKey: cached.accessKey };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
clearCache() {
|
|
223
|
+
this.cache.clear();
|
|
224
|
+
}
|
|
225
|
+
readAndParse(filePath) {
|
|
226
|
+
try {
|
|
227
|
+
const token = readFileSync(filePath, "utf-8").trim();
|
|
228
|
+
if (!token) return null;
|
|
229
|
+
const payload = this.decodePayload(token);
|
|
230
|
+
if (!payload?.exp) return null;
|
|
231
|
+
return {
|
|
232
|
+
token,
|
|
233
|
+
accessKey: payload.access_key ?? "",
|
|
234
|
+
expiresAtMs: payload.exp * 1e3
|
|
235
|
+
};
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
decodePayload(token) {
|
|
241
|
+
try {
|
|
242
|
+
const segments = token.split(".");
|
|
243
|
+
if (segments.length !== 3) return null;
|
|
244
|
+
const padLength = (4 - (segments[1].length % 4 || 4)) % 4;
|
|
245
|
+
const padded = `${segments[1]}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
|
|
246
|
+
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
247
|
+
} catch {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
180
253
|
// src/plugins/platform-plugin.ts
|
|
181
254
|
var DEFAULT_DOMAIN_ENV = "FORCE_AUTHN_INNERAPI_DOMAIN";
|
|
182
255
|
var DEFAULT_ACCESS_KEY_ENV = "FORCE_AUTHN_ACCESS_KEY";
|
|
183
256
|
var DEFAULT_SECRET_KEY_ENV = "FORCE_AUTHN_ACCESS_SECRET";
|
|
184
|
-
var
|
|
257
|
+
var DEFAULT_TOKEN_ENV = "FORCE_AUTHN_TOKEN";
|
|
185
258
|
var DEFAULT_ADMIN_DOMAIN_ENV = "MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX";
|
|
186
259
|
var DEFAULT_CLIENT_TOKEN_ENV = "MIAODA_AUTHN_CODE";
|
|
187
260
|
var MIAODA_CLIENT_TOKEN_HEADER = "X-Miaoda-Client-Token";
|
|
@@ -204,14 +277,16 @@ function registerPlatformPlugin(interceptors, options) {
|
|
|
204
277
|
return void 0;
|
|
205
278
|
}
|
|
206
279
|
ensureNoAccessKeyOverride("platform.defaultClaims", options.defaultClaims);
|
|
280
|
+
let fileTokenProvider;
|
|
281
|
+
if (options.tokenProvider?.type === "file") {
|
|
282
|
+
fileTokenProvider = new FileTokenProvider({
|
|
283
|
+
paths: options.tokenProvider.paths,
|
|
284
|
+
refreshBeforeMs: options.refreshBeforeMs
|
|
285
|
+
});
|
|
286
|
+
}
|
|
207
287
|
const accessKeyEnv = options.accessKeyEnv || DEFAULT_ACCESS_KEY_ENV;
|
|
208
288
|
const secretKeyEnv = options.secretKeyEnv || DEFAULT_SECRET_KEY_ENV;
|
|
209
289
|
const accessKey = options.accessKey ?? (process.env[accessKeyEnv] || "");
|
|
210
|
-
if (!accessKey) {
|
|
211
|
-
throw new Error(
|
|
212
|
-
`\u5E73\u53F0\u9274\u6743\u9700\u8981 access key,\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${accessKeyEnv} \u6216\u4F20 options.accessKey`
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
290
|
const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || "");
|
|
216
291
|
const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;
|
|
217
292
|
const tokenManager = new JWTTokenManager({
|
|
@@ -220,18 +295,22 @@ function registerPlatformPlugin(interceptors, options) {
|
|
|
220
295
|
expireTimeMs,
|
|
221
296
|
refreshBeforeMs: options.refreshBeforeMs
|
|
222
297
|
});
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
if (!staticJwtSource && !secretKey) {
|
|
227
|
-
throw new Error(
|
|
228
|
-
`\u5E73\u53F0\u9274\u6743\u9700\u8981 jwt \u6216 secret key,\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${jwtEnv}(\u63A8\u8350) \u6216 ${secretKeyEnv}`
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
const primaryTokenSource = staticJwtSource ?? tokenManager;
|
|
232
|
-
const fallbackTokenSource = staticJwtSource && secretKey ? tokenManager : null;
|
|
298
|
+
const tokenEnv = options.tokenEnv || DEFAULT_TOKEN_ENV;
|
|
299
|
+
const platformToken = options.token ?? (process.env[tokenEnv] || "");
|
|
300
|
+
const staticTokenSource = platformToken ? new StaticTokenSource(platformToken) : null;
|
|
233
301
|
const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;
|
|
234
302
|
interceptors.request.use((config) => {
|
|
303
|
+
if (fileTokenProvider) {
|
|
304
|
+
const fileToken = fileTokenProvider.getToken();
|
|
305
|
+
if (fileToken) {
|
|
306
|
+
const headers2 = {
|
|
307
|
+
...config.headers,
|
|
308
|
+
"Authorization": `Bearer ${fileToken.token}`,
|
|
309
|
+
"x-api-key": fileToken.accessKey
|
|
310
|
+
};
|
|
311
|
+
return { ...config, headers: headers2 };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
235
314
|
ensureNoAccessKeyOverride("request.platformAuth.customClaims", config.platformAuth?.customClaims);
|
|
236
315
|
const claims = {
|
|
237
316
|
...options.defaultClaims || {},
|
|
@@ -239,11 +318,14 @@ function registerPlatformPlugin(interceptors, options) {
|
|
|
239
318
|
access_key: accessKey
|
|
240
319
|
};
|
|
241
320
|
let token;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
321
|
+
if (staticTokenSource) {
|
|
322
|
+
try {
|
|
323
|
+
token = staticTokenSource.getToken(claims);
|
|
324
|
+
} catch {
|
|
325
|
+
token = tokenManager.getToken(claims);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
token = tokenManager.getToken(claims);
|
|
247
329
|
}
|
|
248
330
|
const headers = {
|
|
249
331
|
...config.headers,
|
|
@@ -653,6 +735,7 @@ var HttpClient = class {
|
|
|
653
735
|
export {
|
|
654
736
|
DEFAULT_CLOCK_TOLERANCE_SEC,
|
|
655
737
|
DEFAULT_JWT_EXPIRE_TIME_MS,
|
|
738
|
+
FileTokenProvider,
|
|
656
739
|
HttpClient,
|
|
657
740
|
HttpError,
|
|
658
741
|
generateJWTToken,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helper/jwt.ts","../src/helper/jwt-manager.ts","../src/plugins/platform-plugin.ts","../src/core/InterceptorManager.ts","../src/core/utils.ts","../src/core/types.ts","../src/core/HttpClient.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from 'crypto';\n\nexport interface CustomClaims {\n tenant_id?: string;\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\n/**\n * Token 来源的最小抽象。`platform-plugin` 用它做 primary / fallback 链\n * (env jwt 优先,sk 自签兜底)。`JWTTokenManager`(下方)和 `StaticJWTSource`\n * (本文件底部)都实现这个接口。\n */\nexport interface TokenSource {\n /**\n * 拿一个可用的 JWT。拿不到必须抛 —— 调用方据此决定走 fallback。\n * 不要返回空串 / null,会让上层 `if (token)` 误判通过。\n */\n getToken(claims: CustomClaims): string;\n}\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 || '',\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\n/**\n * 平台直接派发的静态 JWT(短期凭据,默认 5 天有效),client 不签也不刷,\n * 拿到啥就用啥。过期了 server 端会 401,由 platform-plugin 的 fallback 链\n * 退到 sk 自签(JWTTokenManager)。\n *\n * 用于 §2.2 InnerAPI 鉴权 — ak/sk → jwt + ak 演进:\n * `.env.local` 仅注入 jwt(不再下发 sk),消除 sk 长期落地的泄漏面。\n */\nexport class StaticJWTSource implements TokenSource {\n constructor(private readonly token: string) {\n if (!token) {\n throw new Error('StaticJWTSource: token 不能为空');\n }\n }\n\n getToken(_claims: CustomClaims): string {\n return this.token;\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, StaticJWTSource, TokenSource } 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 * 平台直接派发的静态 JWT(短期凭据,默认 5 天有效)。配置后:\n * - 默认使用此 jwt 调 InnerAPI(不再用 sk 自签),消除 sk 长期落地的泄漏面\n * - 若同时配置了 sk(`accessKey`/`secretKey`/对应 env),作为 fallback 自动启用:\n * * 启动期没拿到 jwt → 退到 sk 自签\n * * (TODO) 运行期 jwt 401 → 退到 sk 自签(本期未做,需 response interceptor)\n * 优先级:`jwt` option > `process.env[jwtEnv ?? 'FORCE_AUTHN_JWT']`\n * 都没配置时:完全退到 sk 自签(向后兼容旧行为)\n */\n jwt?: string;\n jwtEnv?: string;\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_JWT_ENV = 'FORCE_AUTHN_JWT';\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 // ak 必填:不论 token 来源是 envJwt 还是 sk 自签,server 端都靠 x-api-key 路由到对应账号\n // 后再验 jwt 签名,缺 ak 会 silent 退化为 x-api-key:'',真实失败发生在 server 端 401\n // 难追根因。在启动期 fail-fast。\n if (!accessKey) {\n throw new Error(\n `平台鉴权需要 access key,请设置环境变量 ${accessKeyEnv} 或传 options.accessKey`,\n );\n }\n\n // sk 在 jwt 模式下可以缺(envJwt 提供 Bearer);只在两者都缺时才完全无凭证可用。\n // sk 与 envJwt 至少要有一个:由下面 primaryTokenSource 解析时统一判断。\n const secretKey = options.secretKey ?? (process.env[secretKeyEnv] || '');\n\n const expireTimeMs = options.expireTimeMs ?? DEFAULT_JWT_EXPIRE_TIME_MS;\n\n // sk 自签 manager(用作 fallback;旧链路保持唯一来源时也是它)\n // 注:即使 sk 为空也照常实例化 —— 保持跟 SDK 历史行为一致(空 key 签的 JWT\n // 会被 server 端拒,但不在 client 这层抛错,留给上层处理)\n const tokenManager = new JWTTokenManager({\n accessKey,\n secretKey,\n expireTimeMs,\n refreshBeforeMs: options.refreshBeforeMs,\n });\n\n // 平台派发的静态 jwt(优先;§2.2 演进的目标)\n const jwtEnv = options.jwtEnv || DEFAULT_JWT_ENV;\n const platformJwt = options.jwt ?? (process.env[jwtEnv] || '');\n const staticJwtSource = platformJwt ? new StaticJWTSource(platformJwt) : null;\n\n // jwt 与 sk 至少要有一个,否则没有任何凭证可签 Authorization,所有 InnerAPI 必 401\n if (!staticJwtSource && !secretKey) {\n throw new Error(\n `平台鉴权需要 jwt 或 secret key,请设置环境变量 ${jwtEnv}(推荐) 或 ${secretKeyEnv}`,\n );\n }\n\n // primary / fallback 链:有 envJwt 用 envJwt,有 sk 用 sk 兜底\n // 上面已保证 staticJwtSource 或 secretKey 至少一个,所以 primaryTokenSource 永远有效\n const primaryTokenSource: TokenSource = staticJwtSource ?? tokenManager;\n const fallbackTokenSource: TokenSource | null =\n staticJwtSource && secretKey ? tokenManager : null;\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 // 默认走 primary(envJwt 或 sk 自签);primary 抛错才退 fallback。\n // 注:StaticJWTSource 不会抛(token 启动期已 non-empty 校验过);\n // JWTTokenManager 也不会抛 —— 这两个在\"正常配置下\"都能拿到 token。\n // fallback 主要给将来的 401 重试用(本期未做,留 hook)。\n let token: string;\n try {\n token = primaryTokenSource.getToken(claims);\n } catch (err) {\n if (!fallbackTokenSource) throw err;\n token = fallbackTokenSource.getToken(claims);\n }\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 // 返回 sk 自签 manager 供外部使用(如手动清理缓存)。\n // 注:envJwt 模式下 tokenManager 仍然存在(作为 fallback),返回它兼容旧调用方;\n // 静态 jwt 不需要缓存管理,所以不暴露 StaticJWTSource。\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 HttpClientPreInterceptors {\n request?: RequestInterceptor[];\n response?: ResponseInterceptor[];\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n preInterceptors?: HttpClientPreInterceptors;\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, preInterceptors, ...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 this.registerRequestPreInterceptors(preInterceptors?.request);\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 this.registerResponsePreInterceptors(preInterceptors?.response);\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 * 注册请求 preInterceptors\n */\n private registerRequestPreInterceptors(interceptors?: RequestInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.request.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n }\n\n /**\n * 注册响应 preInterceptors\n */\n private registerResponsePreInterceptors(interceptors?: ResponseInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.response.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n }\n\n /**\n * 执行请求拦截器链\n */\n private runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {\n let promise: Promise<RequestConfig> = Promise.resolve(config);\n\n const interceptors: RequestInterceptor[] = [];\n this.interceptors.request.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n promise = promise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return promise;\n }\n\n /**\n * 执行响应拦截器链\n */\n private async runResponseInterceptors(\n promise: Promise<Response>,\n ): Promise<Response> {\n let currentPromise = promise;\n\n // 按顺序执行所有响应拦截器\n const interceptors: ResponseInterceptor[] = [];\n this.interceptors.response.forEach((interceptor) => {\n interceptors.push(interceptor);\n });\n\n for (const interceptor of interceptors) {\n currentPromise = currentPromise.then(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n\n return currentPromise;\n }\n\n /**\n * 构建完整 URL(包含 baseURL 和 query params)\n */\n private buildUrl(url: string, params?: Record<string, any>): string {\n const baseURL = this.defaultConfig.baseURL;\n\n // 拼接 baseURL 和 url(Axios 风格)\n // - 如果 url 是绝对路径(含协议),直接使用 url\n // - 否则拼接 baseURL 和 url,正确处理斜杠\n let combinedUrl: string;\n if (!baseURL || /^https?:\\/\\//i.test(url)) {\n combinedUrl = url;\n } else {\n combinedUrl = baseURL.replace(/\\/+$/, '') + '/' + url.replace(/^\\/+/, '');\n }\n\n const fullUrl = new URL(combinedUrl);\n\n // 协议白名单检查\n const { allowedProtocols } = this.securityConfig;\n if (allowedProtocols && !allowedProtocols.includes(fullUrl.protocol)) {\n throw new Error(\n `Protocol ${fullUrl.protocol} is not allowed. Allowed: ${allowedProtocols.join(', ')}`\n );\n }\n\n // 合并 query params\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n fullUrl.searchParams.set(key, String(value));\n }\n }\n\n return fullUrl.href;\n }\n}\n"],"mappings":"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","names":["normalized"]}
|
|
1
|
+
{"version":3,"sources":["../src/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?: string;\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\n/**\n * Token 来源的最小抽象。`platform-plugin` 用它做 primary / fallback 链\n * (env token 优先,sk 自签兜底)。`JWTTokenManager`(下方)和 `StaticTokenSource`\n * (本文件底部)都实现这个接口。\n */\nexport interface TokenSource {\n /**\n * 拿一个可用的 token(用作 Authorization Bearer)。拿不到必须抛 —— 调用方据此决定走 fallback。\n * 不要返回空串 / null,会让上层 `if (token)` 误判通过。\n */\n getToken(claims: CustomClaims): string;\n}\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 || '',\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\n/**\n * 平台直接派发的静态 token(短期凭据,默认 5 天有效,当前实现为 JWT 但对调用方不透明),\n * client 不签也不刷,拿到啥就用啥。过期了 server 端会 401,由 platform-plugin 的 fallback 链\n * 退到 sk 自签(JWTTokenManager)。\n *\n * 用于 §2.2 InnerAPI 鉴权 — ak/sk → token + ak 演进:\n * `.env.local` 仅注入 token(不再下发 sk),消除 sk 长期落地的泄漏面。\n */\nexport class StaticTokenSource implements TokenSource {\n constructor(private readonly token: string) {\n if (!token) {\n throw new Error('StaticTokenSource: token 不能为空');\n }\n }\n\n getToken(_claims: CustomClaims): string {\n return this.token;\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, StaticTokenSource } 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 /**\n * 平台直接派发的静态 token(短期凭据,默认 5 天有效,当前实现为 JWT)。配置后:\n * - 优先用此 token 调 InnerAPI(不再用 sk 自签),消除 sk 长期落地的泄漏面\n * - 若同时配置了 sk(accessKey/secretKey 或对应 env),sk 自签作为最终 fallback\n * 优先级:`token` option > `process.env[tokenEnv ?? 'FORCE_AUTHN_TOKEN']`\n * 与 `tokenProvider`(文件 token)共存:fileToken > envToken > sk 自签。\n */\n token?: string;\n tokenEnv?: string;\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 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';\nconst DEFAULT_TOKEN_ENV = 'FORCE_AUTHN_TOKEN';\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 // 创建文件 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 // 平台派发的静态 token(可选;有则在 fileToken 之后、sk 自签之前用,消除 sk 长期落地的泄漏面)\n const tokenEnv = options.tokenEnv || DEFAULT_TOKEN_ENV;\n const platformToken = options.token ?? (process.env[tokenEnv] || '');\n const staticTokenSource = platformToken ? new StaticTokenSource(platformToken) : null;\n\n const clientTokenEnv = options.clientTokenEnv || DEFAULT_CLIENT_TOKEN_ENV;\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 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 // 次选 envToken(FORCE_AUTHN_TOKEN env / options.token)—— 短期凭据,跟 fileToken 同样\n // 替代 sk 长期落地。envToken 拿到才用,拿不到继续往下退到 sk 自签。\n // 注:StaticTokenSource.getToken 当前实现是直接返回构造时传入的 token,不会抛 —— 即便将来\n // 改成可能抛的实现(比如带过期校验),也走 sk 自签 fallback。\n let token: string;\n if (staticTokenSource) {\n try {\n token = staticTokenSource.getToken(claims);\n } catch {\n token = tokenManager.getToken(claims);\n }\n } else {\n token = tokenManager.getToken(claims);\n }\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 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 HttpClientPreInterceptors {\n request?: RequestInterceptor[];\n response?: ResponseInterceptor[];\n}\n\nexport interface HttpClientConfig extends RequestConfig {\n platform?: PlatformPluginOptions;\n security?: SecurityConfig;\n preInterceptors?: HttpClientPreInterceptors;\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, preInterceptors, ...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 this.registerRequestPreInterceptors(preInterceptors?.request);\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 this.registerResponsePreInterceptors(preInterceptors?.response);\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 * 注册请求 preInterceptors\n */\n private registerRequestPreInterceptors(interceptors?: RequestInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.request.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\n }\n }\n\n /**\n * 注册响应 preInterceptors\n */\n private registerResponsePreInterceptors(interceptors?: ResponseInterceptor[]) {\n if (!interceptors?.length) {\n return;\n }\n\n for (const interceptor of interceptors) {\n this.interceptors.response.use(\n interceptor.onFulfilled,\n interceptor.onRejected,\n );\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"]}
|
package/package.json
CHANGED