@h-ai/api-client 0.1.0-alpha.70 → 0.1.0-alpha.72
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.en.md +17 -11
- package/README.ja.md +17 -11
- package/README.md +17 -11
- package/dist/index.d.ts +44 -0
- package/dist/index.js +26 -3
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.en.md
CHANGED
|
@@ -152,17 +152,6 @@ Automatic refresh handles an actual HTTP 401 (`Response.status`) or a valid fail
|
|
|
152
152
|
|
|
153
153
|
Object keys are limited to 1024 UTF-8 bytes and reject absolute paths, backslashes, control characters and dot segments. Each public HTTP batch delete accepts 1–1000 entries. `@h-ai/storage/schemas` exposes the shared browser-safe rules. Presign expiry is an integer from 1 to 604800 seconds (Node default 3600, HTTP default 900). Upload signing accepts only key/contentType/expiresIn; contentLength/maxSize/metadata/checksum are unsupported. Invalid input returns HTTP 400 and the typed client maps it to VALIDATION_FAILED. FileUpload limits guide file selection; applications must validate actual objects on the server. Node streaming/checksum/conditional-write options have no default HTTP endpoints. See the [capability and authorization matrix (Chinese)](../storage/README.md#存储输入约束与跨端能力).
|
|
154
154
|
|
|
155
|
-
## Development and testing
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
pnpm --filter @h-ai/api-client test
|
|
159
|
-
pnpm --filter @h-ai/api-client typecheck
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
Clients learn `transport.maxPlaintextBytes` from server key exchange. An explicit local limit selects the smaller value. Capacity failures return `HaiApiClientError.PAYLOAD_TOO_LARGE` (`hai:api-client:020`) with direction, stage and byte diagnostics. See [capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md).
|
|
163
|
-
|
|
164
|
-
serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
|
|
165
|
-
|
|
166
155
|
## Logical request observation
|
|
167
156
|
|
|
168
157
|
`client.observeRequests` observes each logical call once and returns an unsubscribe function. Records contain requestId, procedure, duration, HTTP status, final success/errorCode, and business request attempts. A failed HaiResult with HTTP 200 is a failure. Authentication retries retain the same requestId. Records exclude bodies, tokens, and URL query parameters. Refresh and key exchange are not presented as user operations. close clears subscriptions, so subscribe again after reinitializing.
|
|
@@ -177,6 +166,23 @@ const unsubscribe = apiClient.observeRequests((record) => {
|
|
|
177
166
|
unsubscribe()
|
|
178
167
|
```
|
|
179
168
|
|
|
169
|
+
## Error messages and correlated logs
|
|
170
|
+
|
|
171
|
+
HTTP errors expose localized safe messages followed by `(traceId: …)`, with the same ID in `error.traceId` and `X-Trace-Id`. The framework reuses the request ID to correlate browser and server logs. Sanitized exceptions, stacks and operation context remain in logs; internal cause/ext are not exposed. Field validation returns localized field names and messages only.
|
|
172
|
+
|
|
173
|
+
Use `core.error.toPublic(error, traceId?)` at HTTP/UI boundaries and `core.error.formatMessage(error)` after translating messages; formatting is idempotent. Pass complete Error objects to logs instead of discarding their context. Database failures log driver codes, sanitized messages/stacks and table/column context; SQL text, bound values and connection credentials are excluded.
|
|
174
|
+
|
|
175
|
+
## Development and testing
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pnpm --filter @h-ai/api-client test
|
|
179
|
+
pnpm --filter @h-ai/api-client typecheck
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Clients learn `transport.maxPlaintextBytes` from server key exchange. An explicit local limit selects the smaller value. Capacity failures return `HaiApiClientError.PAYLOAD_TOO_LARGE` (`hai:api-client:020`) with direction, stage and byte diagnostics. See [capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md).
|
|
183
|
+
|
|
184
|
+
serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
|
|
185
|
+
|
|
180
186
|
## License
|
|
181
187
|
|
|
182
188
|
Apache-2.0
|
package/README.ja.md
CHANGED
|
@@ -152,17 +152,6 @@ else {
|
|
|
152
152
|
|
|
153
153
|
キーは UTF-8 で最大 1024 バイトで、絶対パス、バックスラッシュ、制御文字、ドットセグメントを拒否します。公開 HTTP 一括削除は一度に 1–1000 件です。共通規則はブラウザー対応の `@h-ai/storage/schemas` から参照できます。署名有効期限は整数 1–604800 秒(Node 既定 3600、HTTP 既定 900)。アップロード署名の入力は key/contentType/expiresIn のみで、contentLength/maxSize/metadata/checksum は未対応です。不正入力は HTTP 400、typed client では VALIDATION_FAILED です。FileUpload の制限は選択時の補助であり、実際のオブジェクトはサーバーで検証してください。Node のストリーム/checksum/条件付き書き込みには既定の HTTP エンドポイントがありません。[機能と認可の対応表(中国語)](../storage/README.md#存储输入约束与跨端能力)を参照してください。
|
|
154
154
|
|
|
155
|
-
## 開発とテスト
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
pnpm --filter @h-ai/api-client test
|
|
159
|
-
pnpm --filter @h-ai/api-client typecheck
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
`transport.maxPlaintextBytes` は鍵交換で取得します。ローカル上限を明示した場合は小さい値を採用します。容量エラーは `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`)と方向・段階・バイト診断を返します。[容量と移行](../crypto/TRANSPORT-CAPACITY.md)を参照してください。
|
|
163
|
-
|
|
164
|
-
serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
|
|
165
|
-
|
|
166
155
|
## 論理リクエストの監視
|
|
167
156
|
|
|
168
157
|
`client.observeRequests` は論理呼び出しごとに一度通知し、解除関数を返します。requestId、procedure、所要時間、HTTP 状態、最終 success/errorCode、業務リクエストの attempts を含みます。HTTP 200 の失敗 HaiResult も失敗として扱い、認証更新後の再試行も同じ requestId を保持します。本文・Token・URL クエリーは含みません。認証更新や鍵交換をユーザー操作としません。close は購読を解除するため、再初期化後は再購読してください。
|
|
@@ -177,6 +166,23 @@ const unsubscribe = apiClient.observeRequests((record) => {
|
|
|
177
166
|
unsubscribe()
|
|
178
167
|
```
|
|
179
168
|
|
|
169
|
+
## エラー表示とログの関連付け
|
|
170
|
+
|
|
171
|
+
HTTP エラーはローカライズ済みの安全な表示に `(traceId: …)` を付け、同じ ID を error.traceId と X-Trace-Id に返します。requestId を traceId として共有し、ブラウザーとサーバーのログを関連付けます。脱機密化した例外・スタック・操作情報はログに残し、内部 cause/ext は公開しません。項目検証は翻訳済みの項目名とメッセージだけを返します。
|
|
172
|
+
|
|
173
|
+
HTTP/UI 境界には core.error.toPublic(error, traceId?)、再翻訳後の表示には core.error.formatMessage(error) を使い、ID を重複追加しません。ログには完全な Error を渡してください。DB ログにはドライバーコード、脱機密化したメッセージ・スタック、表・列を残し、SQL 本文、バインド値と接続認証情報を記録しません。
|
|
174
|
+
|
|
175
|
+
## 開発とテスト
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pnpm --filter @h-ai/api-client test
|
|
179
|
+
pnpm --filter @h-ai/api-client typecheck
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`transport.maxPlaintextBytes` は鍵交換で取得します。ローカル上限を明示した場合は小さい値を採用します。容量エラーは `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`)と方向・段階・バイト診断を返します。[容量と移行](../crypto/TRANSPORT-CAPACITY.md)を参照してください。
|
|
183
|
+
|
|
184
|
+
serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
|
|
185
|
+
|
|
180
186
|
## ライセンス
|
|
181
187
|
|
|
182
188
|
Apache-2.0
|
package/README.md
CHANGED
|
@@ -152,17 +152,6 @@ else {
|
|
|
152
152
|
|
|
153
153
|
存储 key 按 UTF-8 最多 1024 字节,并拒绝绝对路径、反斜线、控制字符及点路径段;公开 HTTP 批量删除每次 1–1000 项。`@h-ai/storage/schemas` 提供浏览器安全的共同规则。预签名有效期为整数 1–604800 秒(Node 默认 3600,HTTP 默认 900);HTTP 上传仅接受 key/contentType/expiresIn,不支持 contentLength/maxSize/metadata/checksum。无效输入返回 HTTP 400,typed client 映射 VALIDATION_FAILED。FileUpload 的大小/类型限制仅为前端体验,实际对象须由应用服务端复核。Node 流式读写/checksum/条件写等没有默认 HTTP 端点。完整映射与授权边界见 [存储 API 能力](../storage/README.md#存储输入约束与跨端能力)。
|
|
154
154
|
|
|
155
|
-
## 开发测试
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
pnpm --filter @h-ai/api-client test
|
|
159
|
-
pnpm --filter @h-ai/api-client typecheck
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
客户端默认从服务端握手获取 `transport.maxPlaintextBytes`;可显式设置本地同名字段取较小值。容量失败返回 `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`),保留方向、阶段和字节诊断。见 [容量配置与升级](../crypto/TRANSPORT-CAPACITY.md)。
|
|
163
|
-
|
|
164
|
-
serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
|
|
165
|
-
|
|
166
155
|
## 逻辑请求观察
|
|
167
156
|
|
|
168
157
|
`client.observeRequests` 返回可取消的逻辑请求观察:requestId、procedure、耗时、HTTP 状态、最终 success/errorCode 与实际业务请求 attempts。HTTP 200 的失败 HaiResult 也记为失败,自动刷新后的重试保留同一 requestId,整个逻辑调用只产生一次结果。记录不包含正文、Token 或 URL 查询参数。刷新/密钥协商不伪装成用户业务操作;close 清理本实例的订阅,重新 init 后需重新订阅。
|
|
@@ -177,6 +166,23 @@ const unsubscribe = apiClient.observeRequests((record) => {
|
|
|
177
166
|
unsubscribe()
|
|
178
167
|
```
|
|
179
168
|
|
|
169
|
+
## 错误提示与关联日志
|
|
170
|
+
|
|
171
|
+
HTTP 错误只向用户返回本地化的安全提示,末尾显示 `(traceId: …)`;响应同时包含 `error.traceId` 和 `X-Trace-Id`。框架复用本次 requestId 作为 traceId,前后端日志自动关联。底层异常、堆栈和操作上下文经凭据脱敏后写入日志,不通过 cause/ext 向用户公开。字段校验只返回本地化的字段名和错误。
|
|
172
|
+
|
|
173
|
+
`core.error.toPublic(error, traceId?)` 用于接口/UI 边界;`core.error.formatMessage(error)` 用于重新翻译后的提示,重复调用不会重复追加标识。日志传入完整 Error 对象,不能只记录通用标题或提前 String(error) 丢失上下文。数据库失败记录驱动错误码、消息、堆栈及表/列;SQL 原文、绑定值和连接凭据不进入日志。
|
|
174
|
+
|
|
175
|
+
## 开发测试
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pnpm --filter @h-ai/api-client test
|
|
179
|
+
pnpm --filter @h-ai/api-client typecheck
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
客户端默认从服务端握手获取 `transport.maxPlaintextBytes`;可显式设置本地同名字段取较小值。容量失败返回 `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`),保留方向、阶段和字节诊断。见 [容量配置与升级](../crypto/TRANSPORT-CAPACITY.md)。
|
|
183
|
+
|
|
184
|
+
serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
|
|
185
|
+
|
|
180
186
|
## 许可证
|
|
181
187
|
|
|
182
188
|
Apache-2.0
|
package/dist/index.d.ts
CHANGED
|
@@ -583,6 +583,7 @@ declare function createIamAuthActions(client: {
|
|
|
583
583
|
error: {
|
|
584
584
|
code: string | number;
|
|
585
585
|
message: string;
|
|
586
|
+
traceId?: string | undefined;
|
|
586
587
|
httpStatus?: number | undefined;
|
|
587
588
|
system?: string | undefined;
|
|
588
589
|
module?: string | undefined;
|
|
@@ -671,6 +672,7 @@ declare function createIamAuthActions(client: {
|
|
|
671
672
|
error: {
|
|
672
673
|
code: string | number;
|
|
673
674
|
message: string;
|
|
675
|
+
traceId?: string | undefined;
|
|
674
676
|
httpStatus?: number | undefined;
|
|
675
677
|
system?: string | undefined;
|
|
676
678
|
module?: string | undefined;
|
|
@@ -689,6 +691,7 @@ declare function createIamAuthActions(client: {
|
|
|
689
691
|
error: {
|
|
690
692
|
code: string | number;
|
|
691
693
|
message: string;
|
|
694
|
+
traceId?: string | undefined;
|
|
692
695
|
httpStatus?: number | undefined;
|
|
693
696
|
system?: string | undefined;
|
|
694
697
|
module?: string | undefined;
|
|
@@ -707,6 +710,7 @@ declare function createIamAuthActions(client: {
|
|
|
707
710
|
error: {
|
|
708
711
|
code: string | number;
|
|
709
712
|
message: string;
|
|
713
|
+
traceId?: string | undefined;
|
|
710
714
|
httpStatus?: number | undefined;
|
|
711
715
|
system?: string | undefined;
|
|
712
716
|
module?: string | undefined;
|
|
@@ -905,6 +909,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
905
909
|
error: zod.ZodObject<{
|
|
906
910
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
907
911
|
message: zod.ZodString;
|
|
912
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
908
913
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
909
914
|
system: zod.ZodOptional<zod.ZodString>;
|
|
910
915
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -955,6 +960,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
955
960
|
error: zod.ZodObject<{
|
|
956
961
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
957
962
|
message: zod.ZodString;
|
|
963
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
958
964
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
959
965
|
system: zod.ZodOptional<zod.ZodString>;
|
|
960
966
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -973,6 +979,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
973
979
|
error: zod.ZodObject<{
|
|
974
980
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
975
981
|
message: zod.ZodString;
|
|
982
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
976
983
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
977
984
|
system: zod.ZodOptional<zod.ZodString>;
|
|
978
985
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -992,6 +999,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
992
999
|
error: zod.ZodObject<{
|
|
993
1000
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
994
1001
|
message: zod.ZodString;
|
|
1002
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
995
1003
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
996
1004
|
system: zod.ZodOptional<zod.ZodString>;
|
|
997
1005
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1042,6 +1050,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1042
1050
|
error: zod.ZodObject<{
|
|
1043
1051
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1044
1052
|
message: zod.ZodString;
|
|
1053
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1045
1054
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1046
1055
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1047
1056
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1092,6 +1101,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1092
1101
|
error: zod.ZodObject<{
|
|
1093
1102
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1094
1103
|
message: zod.ZodString;
|
|
1104
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1095
1105
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1096
1106
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1097
1107
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1110,6 +1120,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1110
1120
|
error: zod.ZodObject<{
|
|
1111
1121
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1112
1122
|
message: zod.ZodString;
|
|
1123
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1113
1124
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1114
1125
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1115
1126
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1141,6 +1152,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1141
1152
|
error: zod.ZodObject<{
|
|
1142
1153
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1143
1154
|
message: zod.ZodString;
|
|
1155
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1144
1156
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1145
1157
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1146
1158
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1166,6 +1178,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1166
1178
|
error: zod.ZodObject<{
|
|
1167
1179
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1168
1180
|
message: zod.ZodString;
|
|
1181
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1169
1182
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1170
1183
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1171
1184
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1186,6 +1199,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1186
1199
|
error: zod.ZodObject<{
|
|
1187
1200
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1188
1201
|
message: zod.ZodString;
|
|
1202
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1189
1203
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1190
1204
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1191
1205
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1240,6 +1254,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1240
1254
|
error: zod.ZodObject<{
|
|
1241
1255
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1242
1256
|
message: zod.ZodString;
|
|
1257
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1243
1258
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1244
1259
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1245
1260
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1259,6 +1274,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1259
1274
|
error: zod.ZodObject<{
|
|
1260
1275
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1261
1276
|
message: zod.ZodString;
|
|
1277
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1262
1278
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1263
1279
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1264
1280
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1295,6 +1311,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1295
1311
|
error: zod.ZodObject<{
|
|
1296
1312
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1297
1313
|
message: zod.ZodString;
|
|
1314
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1298
1315
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1299
1316
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1300
1317
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1340,6 +1357,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1340
1357
|
error: zod.ZodObject<{
|
|
1341
1358
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1342
1359
|
message: zod.ZodString;
|
|
1360
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1343
1361
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1344
1362
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1345
1363
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1371,6 +1389,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1371
1389
|
error: zod.ZodObject<{
|
|
1372
1390
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1373
1391
|
message: zod.ZodString;
|
|
1392
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1374
1393
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1375
1394
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1376
1395
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1409,6 +1428,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1409
1428
|
error: zod.ZodObject<{
|
|
1410
1429
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1411
1430
|
message: zod.ZodString;
|
|
1431
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1412
1432
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1413
1433
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1414
1434
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1448,6 +1468,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1448
1468
|
error: zod.ZodObject<{
|
|
1449
1469
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1450
1470
|
message: zod.ZodString;
|
|
1471
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1451
1472
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1452
1473
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1453
1474
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1466,6 +1487,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1466
1487
|
error: zod.ZodObject<{
|
|
1467
1488
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1468
1489
|
message: zod.ZodString;
|
|
1490
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1469
1491
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1470
1492
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1471
1493
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1485,6 +1507,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1485
1507
|
error: zod.ZodObject<{
|
|
1486
1508
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1487
1509
|
message: zod.ZodString;
|
|
1510
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1488
1511
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1489
1512
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1490
1513
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1516,6 +1539,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1516
1539
|
error: zod.ZodObject<{
|
|
1517
1540
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1518
1541
|
message: zod.ZodString;
|
|
1542
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1519
1543
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1520
1544
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1521
1545
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1542,6 +1566,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1542
1566
|
error: zod.ZodObject<{
|
|
1543
1567
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1544
1568
|
message: zod.ZodString;
|
|
1569
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1545
1570
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1546
1571
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1547
1572
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1571,6 +1596,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1571
1596
|
error: zod.ZodObject<{
|
|
1572
1597
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1573
1598
|
message: zod.ZodString;
|
|
1599
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1574
1600
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1575
1601
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1576
1602
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1599,6 +1625,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1599
1625
|
error: zod.ZodObject<{
|
|
1600
1626
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1601
1627
|
message: zod.ZodString;
|
|
1628
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1602
1629
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1603
1630
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1604
1631
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1617,6 +1644,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1617
1644
|
error: zod.ZodObject<{
|
|
1618
1645
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1619
1646
|
message: zod.ZodString;
|
|
1647
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1620
1648
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1621
1649
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1622
1650
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1663,6 +1691,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1663
1691
|
error: zod.ZodObject<{
|
|
1664
1692
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1665
1693
|
message: zod.ZodString;
|
|
1694
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1666
1695
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1667
1696
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1668
1697
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1695,6 +1724,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1695
1724
|
error: zod.ZodObject<{
|
|
1696
1725
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1697
1726
|
message: zod.ZodString;
|
|
1727
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1698
1728
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1699
1729
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1700
1730
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1736,6 +1766,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1736
1766
|
error: zod.ZodObject<{
|
|
1737
1767
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1738
1768
|
message: zod.ZodString;
|
|
1769
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1739
1770
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1740
1771
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1741
1772
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1754,6 +1785,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1754
1785
|
error: zod.ZodObject<{
|
|
1755
1786
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1756
1787
|
message: zod.ZodString;
|
|
1788
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1757
1789
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1758
1790
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1759
1791
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1781,6 +1813,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1781
1813
|
error: zod.ZodObject<{
|
|
1782
1814
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1783
1815
|
message: zod.ZodString;
|
|
1816
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1784
1817
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1785
1818
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1786
1819
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1805,6 +1838,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1805
1838
|
error: zod.ZodObject<{
|
|
1806
1839
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1807
1840
|
message: zod.ZodString;
|
|
1841
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1808
1842
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1809
1843
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1810
1844
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1840,6 +1874,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1840
1874
|
error: zod.ZodObject<{
|
|
1841
1875
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1842
1876
|
message: zod.ZodString;
|
|
1877
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1843
1878
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1844
1879
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1845
1880
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1865,6 +1900,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1865
1900
|
error: zod.ZodObject<{
|
|
1866
1901
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1867
1902
|
message: zod.ZodString;
|
|
1903
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1868
1904
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1869
1905
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1870
1906
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1883,6 +1919,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1883
1919
|
error: zod.ZodObject<{
|
|
1884
1920
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1885
1921
|
message: zod.ZodString;
|
|
1922
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1886
1923
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1887
1924
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1888
1925
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1913,6 +1950,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
1913
1950
|
error: zod.ZodObject<{
|
|
1914
1951
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
1915
1952
|
message: zod.ZodString;
|
|
1953
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
1916
1954
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
1917
1955
|
system: zod.ZodOptional<zod.ZodString>;
|
|
1918
1956
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2047,6 +2085,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
2047
2085
|
error: zod.ZodObject<{
|
|
2048
2086
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
2049
2087
|
message: zod.ZodString;
|
|
2088
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
2050
2089
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
2051
2090
|
system: zod.ZodOptional<zod.ZodString>;
|
|
2052
2091
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2078,6 +2117,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
2078
2117
|
error: zod.ZodObject<{
|
|
2079
2118
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
2080
2119
|
message: zod.ZodString;
|
|
2120
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
2081
2121
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
2082
2122
|
system: zod.ZodOptional<zod.ZodString>;
|
|
2083
2123
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2177,6 +2217,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
2177
2217
|
error: zod.ZodObject<{
|
|
2178
2218
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
2179
2219
|
message: zod.ZodString;
|
|
2220
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
2180
2221
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
2181
2222
|
system: zod.ZodOptional<zod.ZodString>;
|
|
2182
2223
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2225,6 +2266,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
2225
2266
|
error: zod.ZodObject<{
|
|
2226
2267
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
2227
2268
|
message: zod.ZodString;
|
|
2269
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
2228
2270
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
2229
2271
|
system: zod.ZodOptional<zod.ZodString>;
|
|
2230
2272
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2271,6 +2313,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
2271
2313
|
error: zod.ZodObject<{
|
|
2272
2314
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
2273
2315
|
message: zod.ZodString;
|
|
2316
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
2274
2317
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
2275
2318
|
system: zod.ZodOptional<zod.ZodString>;
|
|
2276
2319
|
module: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2299,6 +2342,7 @@ declare const defaultApiContract: _h_ai_api_contract.CreatedApiContract<{
|
|
|
2299
2342
|
error: zod.ZodObject<{
|
|
2300
2343
|
code: zod.ZodUnion<readonly [zod.ZodString, zod.ZodNumber]>;
|
|
2301
2344
|
message: zod.ZodString;
|
|
2345
|
+
traceId: zod.ZodOptional<zod.ZodString>;
|
|
2302
2346
|
httpStatus: zod.ZodOptional<zod.ZodNumber>;
|
|
2303
2347
|
system: zod.ZodOptional<zod.ZodString>;
|
|
2304
2348
|
module: zod.ZodOptional<zod.ZodString>;
|
package/dist/index.js
CHANGED
|
@@ -469,11 +469,30 @@ async function callObservedProcedure(rawClient, path, args, observers) {
|
|
|
469
469
|
const provided = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
470
470
|
const providedContext = provided.context && typeof provided.context === "object" ? provided.context : {};
|
|
471
471
|
const options = { ...provided, context: { ...providedContext, [REQUEST_OBSERVATION]: record } };
|
|
472
|
-
|
|
472
|
+
let result = await callProcedure(rawClient, path, [args[0], options]);
|
|
473
473
|
record.duration = Math.max(0, Date.now() - record.timestamp);
|
|
474
474
|
record.success = !(result && typeof result === "object" && "success" in result && result.success === false);
|
|
475
|
-
if (result && typeof result === "object" && "error" in result && result.error && typeof result.error === "object" && "code" in result.error)
|
|
475
|
+
if (result && typeof result === "object" && "error" in result && result.error && typeof result.error === "object" && "code" in result.error) {
|
|
476
476
|
record.errorCode = String(result.error.code);
|
|
477
|
+
if ("message" in result.error && typeof result.error.message === "string") {
|
|
478
|
+
const error = result.error;
|
|
479
|
+
const receivedId = error.traceId && /^[\w.:-]{1,128}$/.test(error.traceId) ? error.traceId : record.requestId;
|
|
480
|
+
record.requestId = receivedId;
|
|
481
|
+
const safe = core.error.toPublic({ ...error, publicExt: error.ext }, receivedId);
|
|
482
|
+
if (error.cause && typeof error.cause === "object" && !Array.isArray(error.cause)) {
|
|
483
|
+
const metadata = {};
|
|
484
|
+
for (const key of ["orpcCode", "status", "transportCode", "direction", "stage", "limitBytes", "actualBytes"]) {
|
|
485
|
+
const value = Reflect.get(error.cause, key);
|
|
486
|
+
if (typeof value === "number" || typeof value === "string")
|
|
487
|
+
metadata[key] = core.sanitize.sanitizeSensitiveValue(value, key);
|
|
488
|
+
}
|
|
489
|
+
if (Object.keys(metadata).length)
|
|
490
|
+
safe.cause = metadata;
|
|
491
|
+
}
|
|
492
|
+
core.logger.error("API request failed", { traceId: safe.traceId, requestId: record.requestId, procedure: record.procedure, status: record.status, error: result.error });
|
|
493
|
+
result = { ...result, error: safe };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
477
496
|
for (const observer of observers ?? []) {
|
|
478
497
|
try {
|
|
479
498
|
const observed = observer(Object.freeze({ ...record }));
|
|
@@ -551,8 +570,12 @@ function buildRawClient(contract, config, tokenManager, transport) {
|
|
|
551
570
|
record.url = core.sanitize.sanitizeUrl(url.toString());
|
|
552
571
|
}
|
|
553
572
|
const response = await fetchWithRefresh(request, init, config, tokenManager, transport, record);
|
|
554
|
-
if (record)
|
|
573
|
+
if (record) {
|
|
555
574
|
record.status = response.status;
|
|
575
|
+
const traceId = response.headers.get("x-trace-id") ?? response.headers.get("x-request-id");
|
|
576
|
+
if (traceId && /^[\w.:-]{1,128}$/.test(traceId))
|
|
577
|
+
record.requestId = traceId;
|
|
578
|
+
}
|
|
556
579
|
return response;
|
|
557
580
|
}
|
|
558
581
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api-client-auth.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/api-client-i18n.ts","../src/api-client-iam.ts","../src/api-client-types.ts","../src/api-client-main.ts"],"names":["core"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/api-client — Token 存储与刷新管理\n *\n * 提供两个内置 Token 存储适配器(httpOnly Cookie / 内存)和一个 Token 管理器\n * (封装存储读写、自动刷新、并发去重、回调通知)。\n *\n * @example 浏览器 SPA\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const initialized = await apiClient.init({\n * baseUrl: 'https://api.example.com/api/v1',\n * auth: { storage: apiClient.tokenStorage.httpOnlyCookie() },\n * })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * ```\n *\n * @example Node.js 测试\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * await storage.clear()\n * ```\n *\n * @module api-client-auth\n */\n\nimport type { TokenPair, TokenStorage } from './api-client-types.js'\nimport { core } from '@h-ai/core'\nimport { z } from 'zod'\n\nconst logger = core.logger.child({ module: 'api-client', scope: 'auth' })\n\n/**\n * httpOnly cookie 模式哨兵值。\n * `apiClient.tokenStorage.httpOnlyCookie()` 的 `getRefreshToken()` 返回此值,\n * 告知 `doRefresh` 应依赖浏览器自动发送的 httpOnly Cookie,\n * 而不是将 refreshToken 写入请求体。\n */\nconst HTTPONLY_COOKIE_SENTINEL = '__httponly_cookie__'\n\n// ─── 内存存储 ─────────────────────────────────────────────────────────────────\n\n/**\n * 创建仅属于本适配器实例的内存凭据存储,页面刷新或进程结束后丢失。\n * SSR 应为每个请求创建客户端和存储实例,避免不同用户共享默认单例。\n * 通过 client.auth 更新已绑定的存储,才能撤销旧代际刷新;直接存储写入不提供这种保证。\n * @returns 两个初始为空的 Token 槽位及清理操作。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * const saved = await storage.getAccessToken()\n * await storage.clear()\n * // saved 为本实例的测试值;不把 Token 写入 Web Storage。\n * ```\n */\nexport function createMemoryTokenStorage(): TokenStorage {\n let accessToken: string | null = null\n let refreshToken: string | null = null\n return {\n async getAccessToken() { return accessToken },\n async setAccessToken(token) { accessToken = token },\n async getRefreshToken() { return refreshToken },\n async setRefreshToken(token) { refreshToken = token },\n async clear() {\n accessToken = null\n refreshToken = null\n },\n }\n}\n\n// ─── httpOnly Cookie 存储 ────────────────────────────────────────────────────\n\n/**\n * 创建浏览器使用的内存 access token 与服务端 httpOnly refresh Cookie 适配器。\n * refresh getter 返回内部哨兵,setter 不修改 Cookie;clear 只清除内存 access token。\n * 刷新请求使用 credentials: include;服务端须实际设置/撤销 Cookie,配置适当的 Secure、SameSite、Path 与跨域凭据策略。\n * HttpOnly 限制脚本直接读取 Cookie,不能阻止已执行的恶意脚本代用户发请求;服务端仍须授权和防护 CSRF。\n * @returns 独立内存 access token 与 Cookie 模式标记;不接触 localStorage。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: { storage: apiClient.tokenStorage.httpOnlyCookie() } })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const me = await apiClient.iam.auth.currentUser()\n * if (!me.success) throw new Error(me.error.message)\n * const userId = me.data.id\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport function createHttpOnlyCookieTokenStorage(): TokenStorage {\n // access token 保存在内存中,用于注入 Authorization 请求头\n let memoryAccessToken: string | null = null\n return {\n async getAccessToken() { return memoryAccessToken },\n async setAccessToken(token) { memoryAccessToken = token },\n // refresh token 由服务端通过 Set-Cookie 设置为 httpOnly cookie;\n // JS 不可见,返回哨兵值让 TokenManager 识别此模式。\n async getRefreshToken() { return HTTPONLY_COOKIE_SENTINEL },\n // refresh token 由服务端管理,JS 端无法写入 httpOnly cookie,忽略此调用。\n async setRefreshToken(_token) { /* 由服务端 Set-Cookie 响应头管理,客户端无需操作 */ },\n async clear() {\n memoryAccessToken = null\n // httpOnly cookie 的清除必须由服务端 logout 端点完成(Set-Cookie: Max-Age=0);\n // 客户端无法通过 JS 直接删除 httpOnly cookie。\n },\n }\n}\n\n// ─── Token 管理器 ─────────────────────────────────────────────────────────────\n\n/** 校验 refresh 响应体中的 Token 字段;httpOnly cookie 模式允许不返回 refreshToken。 */\nconst RefreshTokenPayloadSchema = z.object({\n accessToken: z.string().min(1),\n refreshToken: z.string().min(1).optional(),\n expiresIn: z.number().optional(),\n tokenType: z.string().optional(),\n})\n\n/**\n * 当前代际刷新成功后的同步通知;参数包含敏感凭据,不应记录 Token。\n * 回调在持久写入成功后调用,应自行处理业务异常,不能把抛错当成刷新重试机制。\n *\n * @example 模块内部上下文\n * ```ts\n * const observed: number[] = []\n * const callback: RefreshCallback = tokens => { observed.push(tokens.expiresIn) }\n * callback({ accessToken: 'test-access', expiresIn: 3600, tokenType: 'Bearer' })\n * ```\n */\ntype RefreshCallback = (tokens: TokenPair) => void\n\n/**\n * 创建模块内部 Token 管理器;包用户通过 apiClient.auth 使用,包入口不导出此工厂。\n * 同一代际的并发刷新共享请求;clear/setTokens 同步撤销旧代际,所有存储变更串行提交。\n * 迟到响应不能覆盖新账号、恢复退出凭据或触发旧回调;已开始的存储写入须完成后才能执行后续身份变更。\n * 4xx、无效响应和非 Cookie 模式缺少 refreshToken 会尝试清理凭据;5xx、网络/超时保留凭据并返回 null。\n * 无刷新凭据时通知失败并返回 null;存储读取失败可拒绝 Promise,setTokens/clear 的存储失败向调用方传播。\n * @param storage - 独占的安全存储适配器。\n * @param refreshEndpointUrl - baseUrl 与 refreshPath 组合后的完整 URL。\n * @param fetchFn - 实际 fetch;Cookie 模式发送凭据,普通模式发送 JSON refreshToken。\n * @param onRefreshFailed - 当前代际明确失效后的同步通知。\n * @param refreshTimeoutMs - fetch 中断超时毫秒数;依赖 fetch 实现响应 signal。\n * @returns 内部管理器;不要绕过它直接更新已绑定的 storage。\n *\n * @example 模块内部上下文\n * ```ts\n * const storage = createMemoryTokenStorage()\n * const fetchTokens: typeof fetch = async () => Response.json({ data: { accessToken: 'test-access-next', refreshToken: 'test-refresh-next', expiresIn: 3600 } })\n * const manager = createTokenManager(storage, 'https://api.example.com/api/v1/auth/refresh', fetchTokens)\n * await manager.setTokens({ accessToken: 'test-access', refreshToken: 'test-refresh', expiresIn: 3600, tokenType: 'Bearer' })\n * const off = manager.onTokenRefreshed(tokens => { const lifetime = tokens.expiresIn })\n * try { const refreshed = await manager.refresh() }\n * finally { off(); await manager.clear() }\n * ```\n */\nexport function createTokenManager(\n storage: TokenStorage,\n refreshEndpointUrl: string,\n fetchFn: typeof globalThis.fetch,\n onRefreshFailed?: () => void,\n refreshTimeoutMs: number = 10_000,\n) {\n const callbacks: RefreshCallback[] = []\n let refreshPromise: Promise<TokenPair | null> | null = null\n let refreshController: AbortController | null = null\n let generation = 0\n let storageMutation: Promise<void> = Promise.resolve()\n\n /**\n * 按调用顺序提交异步存储写入;单次失败只拒绝该调用,后续清理/登录仍可运行。\n * @param operation - 本次需要排序的写入操作。\n * @returns 本次操作结果,不吞掉当前写入故障。\n *\n * @example 模块内部上下文\n * ```ts\n * const order: string[] = []\n * await mutateStorage(async () => { order.push('login') })\n * await mutateStorage(async () => { order.push('logout') })\n * ```\n */\n function mutateStorage<T>(operation: () => Promise<T>): Promise<T> {\n const pending = storageMutation.then(operation)\n storageMutation = pending.then(() => {}, () => {})\n return pending\n }\n\n /**\n * 同步推进身份代际并移除旧刷新去重状态;返回旧控制器供调用方排好新写入后中断。\n * @returns 旧刷新控制器或 null;即使底层忽略 abort,代际检查仍拒绝旧结果提交。\n *\n * @example 模块内部上下文\n * ```ts\n * const old = invalidateRefresh()\n * // 生产调用先排入新身份存储变更,再触发可能重入的 abort 监听器。\n * old?.abort()\n * ```\n */\n function invalidateRefresh(): AbortController | null {\n const previousController = refreshController\n generation++\n refreshController = null\n refreshPromise = null\n return previousController\n }\n\n /**\n * 仅当前刷新代际可尝试清理存储并通知失效;旧账号迟到 401 不影响新身份。\n * @param refreshGeneration - 发起刷新时记录的身份代际。\n * @returns 清理完成;存储失败拒绝,由 doRefresh 的异常分支处理。\n *\n * @example 模块内部上下文\n * ```ts\n * await failRefresh(generation)\n * // 传入旧 generation 会在串行写入队列中再次校验并跳过。\n * ```\n */\n async function failRefresh(refreshGeneration: number): Promise<void> {\n await mutateStorage(async () => {\n if (refreshGeneration !== generation)\n return\n await storage.clear()\n if (refreshGeneration === generation)\n onRefreshFailed?.()\n })\n }\n\n /**\n * 同一代际只发起一个刷新请求;旧请求 finally 不能清除新身份的在途状态。\n * @returns 成功且仍有效的 Token,或 null;读取适配器失败可能拒绝。\n *\n * @example 模块内部上下文\n * ```ts\n * const [first, second] = await Promise.all([refresh(), refresh()])\n * // 两个调用共享在途 fetch;结果仍以各自读取到的当前代际为准。\n * ```\n */\n async function refresh(): Promise<TokenPair | null> {\n // 并发去重:同一时刻只发一次 refresh,其它调用复用同一个 Promise\n if (refreshPromise)\n return refreshPromise\n const controller = new AbortController()\n const pending = doRefresh(generation, controller)\n refreshController = controller\n refreshPromise = pending\n try {\n return await pending\n }\n finally {\n // 旧请求结束不能移除换账号后新一轮刷新的去重状态。\n if (refreshPromise === pending) {\n refreshPromise = null\n refreshController = null\n }\n }\n }\n\n /**\n * 等待身份写入完成后读取刷新凭据,执行超时请求并在每个异步边界校验代际。\n * 4xx/无效数据尝试清理;5xx/网络故障保留凭据。只有当前代际成功写入后才通知订阅者。\n * @param refreshGeneration - 请求开始时的代际。\n * @param controller - 本次请求中断控制器;底层 fetch 应响应 signal。\n * @returns 当前刷新 Token 或 null;清理/回调等请求段异常记录为失败,不声称持久清理成功。\n *\n * @example 模块内部上下文\n * ```ts\n * const controller = new AbortController()\n * const tokens = await doRefresh(generation, controller)\n * if (tokens) { const lifetime = tokens.expiresIn }\n * ```\n */\n async function doRefresh(refreshGeneration: number, controller: AbortController): Promise<TokenPair | null> {\n // 登录/退出可能正在异步落盘,刷新只能读取其完成后的凭据。\n await storageMutation\n if (refreshGeneration !== generation)\n return null\n const refreshToken = await storage.getRefreshToken()\n if (refreshGeneration !== generation)\n return null\n if (!refreshToken) {\n logger.warn('Token refresh skipped, no refresh token available')\n onRefreshFailed?.()\n return null\n }\n\n logger.debug('Refreshing token')\n // 超时保护:避免 refresh 接口挂死时 dedup Promise 永不 resolve,连带所有等待者 hang。\n const timeoutId = setTimeout(() => controller.abort(), refreshTimeoutMs)\n try {\n // httpOnly cookie 模式:浏览器自动发送 Cookie,请求体无需携带 refreshToken;\n // 同时设置 credentials:'include' 以支持跨域 Cookie 发送。\n const useHttpOnlyCookie = refreshToken === HTTPONLY_COOKIE_SENTINEL\n const requestInit: RequestInit = {\n method: 'POST',\n body: useHttpOnlyCookie ? undefined : JSON.stringify({ refreshToken }),\n credentials: useHttpOnlyCookie ? 'include' : 'same-origin',\n signal: controller.signal,\n }\n if (!useHttpOnlyCookie) {\n requestInit.headers = { 'Content-Type': 'application/json' }\n }\n const response = await fetchFn(refreshEndpointUrl, requestInit)\n if (refreshGeneration !== generation)\n return null\n\n if (!response.ok) {\n // 仅在服务端明确返回 401/4xx 时才清除 Token(refresh token 已失效);\n // 5xx 作为服务端临时故障,保留 Token 让下一次请求重试。\n if (response.status >= 400 && response.status < 500) {\n await failRefresh(refreshGeneration)\n }\n else {\n logger.warn('Token refresh transient failure, keeping tokens', { status: response.status })\n }\n return null\n }\n\n const body = await response.json() as { data?: unknown }\n if (refreshGeneration !== generation)\n return null\n const parsed = RefreshTokenPayloadSchema.safeParse(readTokenPayload(body.data))\n if (!parsed.success) {\n logger.warn('Token refresh returned invalid data', { issues: parsed.error.issues })\n await failRefresh(refreshGeneration)\n return null\n }\n if (!useHttpOnlyCookie && !parsed.data.refreshToken) {\n logger.warn('Token refresh returned invalid data', { issues: [{ path: ['refreshToken'], message: 'Required' }] })\n await failRefresh(refreshGeneration)\n return null\n }\n\n // 默认值兜底,避免不安全强转\n const refreshTokenForStorage = parsed.data.refreshToken ?? HTTPONLY_COOKIE_SENTINEL\n const tokens: TokenPair = {\n accessToken: parsed.data.accessToken,\n refreshToken: refreshTokenForStorage,\n expiresIn: parsed.data.expiresIn ?? 3600,\n tokenType: 'Bearer',\n }\n const committed = await mutateStorage(async () => {\n if (refreshGeneration !== generation)\n return false\n await storage.setAccessToken(tokens.accessToken)\n if (refreshGeneration !== generation)\n return false\n await storage.setRefreshToken(refreshTokenForStorage)\n return refreshGeneration === generation\n })\n if (!committed || refreshGeneration !== generation)\n return null\n logger.info('Token refreshed successfully')\n for (const cb of callbacks) {\n if (refreshGeneration !== generation)\n return null\n cb(tokens)\n }\n return refreshGeneration === generation ? tokens : null\n }\n catch (error) {\n if (refreshGeneration !== generation)\n return null\n // 网络或 fetch 本身抛异常(超时、断网、CORS)视为可重试的暂时性错误,\n // 不清除 Token,仅记录日志。调用方可在下次请求时重试。\n logger.error('Token refresh failed (transient)', { error })\n return null\n }\n finally {\n clearTimeout(timeoutId)\n }\n }\n\n return {\n storage,\n refresh,\n async setTokens(tokens: TokenPair) {\n const previousController = invalidateRefresh()\n const mutationGeneration = generation\n const pending = mutateStorage(async () => {\n if (mutationGeneration !== generation)\n return\n // 替换完整凭据:新账号未提供 refresh token 时不能继续使用旧账号的值。\n await storage.clear()\n if (mutationGeneration !== generation)\n return\n await storage.setAccessToken(tokens.accessToken)\n if (tokens.refreshToken && mutationGeneration === generation)\n await storage.setRefreshToken(tokens.refreshToken)\n })\n // 先排好新身份写入,再触发 abort 事件;可重入的监听器只能读取新操作后的存储。\n previousController?.abort()\n await pending\n },\n async clear() {\n const previousController = invalidateRefresh()\n const pending = mutateStorage(() => storage.clear())\n previousController?.abort()\n await pending\n },\n onTokenRefreshed(callback: RefreshCallback): () => void {\n callbacks.push(callback)\n return () => {\n const idx = callbacks.indexOf(callback)\n if (idx >= 0)\n callbacks.splice(idx, 1)\n }\n },\n }\n}\n\n/**\n * Token 管理器内部契约,从真实工厂返回值推导;不属于包公开类型。\n *\n * @example 模块内部上下文\n * ```ts\n * const manager: TokenManager = createTokenManager(createMemoryTokenStorage(), 'https://api.example.com/api/v1/auth/refresh', fetch)\n * await manager.clear()\n * // clear 先废弃旧代际,再等待已有写入及本次清理完成。\n * ```\n */\nexport type TokenManager = ReturnType<typeof createTokenManager>\n\n/**\n * 从直接 tokens 或带 tokens 字段的 data 提取待校验内容;本函数不证明字段合法。\n * @param data - 已解析 JSON 包络的 data。\n * @returns 交给 RefreshTokenPayloadSchema 校验的未知值。\n *\n * @example 模块内部上下文\n * ```ts\n * const candidate = readTokenPayload({ tokens: { accessToken: 'test-access', expiresIn: 3600 } })\n * const parsed = RefreshTokenPayloadSchema.safeParse(candidate)\n * if (parsed.success) { const accessToken = parsed.data.accessToken }\n * ```\n */\nfunction readTokenPayload(data: unknown): unknown {\n if (typeof data === 'object' && data !== null && 'tokens' in data) {\n return (data as { tokens: unknown }).tokens\n }\n return data\n}\n","{\n \"apiClient_networkError\": \"Network request failed\",\n \"apiClient_timeout\": \"Request timeout\",\n \"apiClient_serverError\": \"Server error ({status})\",\n \"apiClient_unauthorized\": \"Unauthorized, please log in again\",\n \"apiClient_forbidden\": \"Access forbidden\",\n \"apiClient_notFound\": \"Resource not found\",\n \"apiClient_validationFailed\": \"Request validation failed\",\n \"apiClient_tokenRefreshFailed\": \"Token refresh failed, please log in again\",\n \"apiClient_notInitialized\": \"API client not initialized, please call apiClient.init() first\",\n \"apiClient_alreadyInitialized\": \"API client is already initialized; call apiClient.close() before init()\",\n \"apiClient_configError\": \"API client config error: {error}\",\n \"apiClient_responseValidationFailed\": \"Response validation failed\",\n \"apiClient_unknown\": \"Unknown error\"\n}\n","{\n \"apiClient_networkError\": \"ネットワークリクエストに失敗しました\",\n \"apiClient_timeout\": \"リクエストがタイムアウトしました\",\n \"apiClient_serverError\": \"サーバーエラー({status})\",\n \"apiClient_unauthorized\": \"認証されていません。再度ログインしてください\",\n \"apiClient_forbidden\": \"アクセスが拒否されました\",\n \"apiClient_notFound\": \"リソースが見つかりません\",\n \"apiClient_validationFailed\": \"リクエストの検証に失敗しました\",\n \"apiClient_tokenRefreshFailed\": \"トークンの更新に失敗しました。再度ログインしてください\",\n \"apiClient_notInitialized\": \"APIクライアントが初期化されていません。先にapiClient.init()を呼び出してください\",\n \"apiClient_alreadyInitialized\": \"APIクライアントは初期化済みです。init()の前にapiClient.close()を呼び出してください\",\n \"apiClient_configError\": \"APIクライアントの設定エラー: {error}\",\n \"apiClient_responseValidationFailed\": \"レスポンスの検証に失敗しました\",\n \"apiClient_unknown\": \"不明なエラー\"\n}\n","{\n \"apiClient_networkError\": \"网络请求失败\",\n \"apiClient_timeout\": \"请求超时\",\n \"apiClient_serverError\": \"服务器错误({status})\",\n \"apiClient_unauthorized\": \"未授权,请重新登录\",\n \"apiClient_forbidden\": \"无权限访问\",\n \"apiClient_notFound\": \"请求的资源不存在\",\n \"apiClient_validationFailed\": \"请求参数校验失败\",\n \"apiClient_tokenRefreshFailed\": \"Token刷新失败,请重新登录\",\n \"apiClient_notInitialized\": \"API 客户端未初始化,请先调用 apiClient.init()\",\n \"apiClient_alreadyInitialized\": \"API 客户端已初始化,请先调用 apiClient.close() 再重新初始化\",\n \"apiClient_configError\": \"API 客户端配置错误:{error}\",\n \"apiClient_responseValidationFailed\": \"响应数据校验失败\",\n \"apiClient_unknown\": \"未知错误\"\n}\n","/**\n * @h-ai/api-client — i18n\n *\n * 模块 i18n 文案访问入口。\n * @module api-client-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/**\n * 模块内合法翻译键;由中文词典推导并与现有英文词典配套维护。\n *\n * @example 模块内部上下文\n * ```ts\n * const key: ApiClientMessageKey = 'apiClient_notInitialized'\n * const message = apiClientM(key)\n * ```\n */\ntype ApiClientMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取 API Client 本地化消息;跟随 Core 当前 locale,不记录凭据或响应正文。\n * @param key - 真实模块消息键。\n * @param options - 语言覆盖及 params 插值选项。\n * @returns 已插值的中文或英文消息。\n *\n * @example 模块内部上下文\n * ```ts\n * const message = apiClientM('apiClient_configError', { locale: 'en-US', params: { error: 'Invalid endpoint' } })\n * ```\n */\nexport const apiClientM = core.i18n.createMessageGetter<ApiClientMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n","/** 认证表单动作:统一选择登录方式及安全 Token 存储。 */\nimport type { apiContract, IamLoginInput, IamRegisterInput } from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { ApiClient } from './api-client-types.js'\n\n/**\n * 认证表单登录输入;默认 password,ldap 使用 identifier/password,email/sms 使用 identifier/code。\n * method/code 为客户端表单辅助字段,按所选方式映射到标准 IAM procedure。\n *\n * @example\n * ```ts\n * import type { IamAuthLoginInput } from '@h-ai/api-client'\n * const input: IamAuthLoginInput = { identifier: 'alice@example.com', password: '', method: 'email', code: '123456' }\n * ```\n */\nexport type IamAuthLoginInput = IamLoginInput & { method?: 'password' | 'ldap' | 'email' | 'sms', code?: string }\n\n/**\n * 将含标准 IAM 契约的客户端绑定为认证表单动作;包用户通过 apiClient.createAuthActions 调用。\n * 登录/注册成功后等待 client.auth.setTokens,失败 HaiResult 原样返回;存储失败拒绝 Promise,不能据服务端成功就显示已登录。\n * 调用方必须先 init 并启用 auth;未启用 auth 时客户端 setTokens 不会建立 Token 管理器。\n * @param client - 具有标准 IAM procedure 与 setTokens 能力的已初始化客户端。\n * @param client.iam - 标准 IAM 契约的领域分组。\n * @param client.iam.auth - 公开配置、登录、注册、验证码和密码重置 procedure。\n * @param client.auth - 认证成功后的凭据写入能力(setTokens)。\n * @returns publicConfig、按方式登录、注册、验证码和密码重置动作。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const actions = apiClient.createAuthActions(apiClient)\n * // 运行时凭据来自已验证的表单;此处只请求公开配置,不提交示例密码。\n * const config = await actions.publicConfig()\n * if (!config.success) throw new Error(config.error.message)\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport function createIamAuthActions(client: {\n iam: { auth: Pick<ApiClient<{ iam: typeof apiContract.iam }>['iam']['auth'], 'publicConfig' | 'login' | 'loginWithLdap' | 'loginWithOtp' | 'register' | 'sendOtp' | 'requestPasswordReset' | 'confirmPasswordReset'> }\n auth: Pick<ApiClient<{ iam: typeof apiContract.iam }>['auth'], 'setTokens'>\n}) {\n /**\n * 认证成功后等待凭据保存;失败 HaiResult 不写存储,写入失败向调用方拒绝。\n * @param result - 标准 IAM 登录/注册结果,成功数据含 TokenPair。\n * @returns 同一结果;不能把服务端成功与本地安全存储成功混为一谈。\n *\n * @example 模块内部上下文\n * ```ts\n * const failure = { success: false as const, error: { code: 'test:denied', message: 'Denied', httpStatus: 401 } }\n * const result = await save(failure)\n * if (!result.success) { const message = result.error.message }\n * ```\n */\n async function save<T extends { tokens: Parameters<typeof client.auth.setTokens>[0] }>(result: HaiResult<T>) {\n if (result.success)\n await client.auth.setTokens(result.data.tokens)\n return result\n }\n return {\n publicConfig: () => client.iam.auth.publicConfig(),\n login: async (input: IamAuthLoginInput) => {\n const result = input.method === 'ldap'\n ? await client.iam.auth.loginWithLdap({ username: input.identifier, password: input.password })\n : input.method === 'email' || input.method === 'sms'\n ? await client.iam.auth.loginWithOtp({ identifier: input.identifier, code: input.code ?? '' })\n : await client.iam.auth.login({ identifier: input.identifier, password: input.password })\n return save(result)\n },\n register: async (input: IamRegisterInput) => save(await client.iam.auth.register(input)),\n sendOtp: (identifier: string) => client.iam.auth.sendOtp({ identifier }),\n requestPasswordReset: (identifier: string) => client.iam.auth.requestPasswordReset({ identifier }),\n confirmPasswordReset: (token: string, newPassword: string) => client.iam.auth.confirmPasswordReset({ token, newPassword }),\n }\n}\n","/**\n * @h-ai/api-client — 类型与错误码\n *\n * 公共类型(TokenPair、TokenStorage、ApiClientConfig 等)与标准错误集合(HaiApiClientError)。\n * 不包含实现,仅用于跨文件共享与对外导出。\n * @module api-client-types\n */\n\nimport type { ErrorInfo, HaiResult } from '@h-ai/core'\nimport type { CryptoFunctions } from '@h-ai/crypto'\nimport type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'\nimport type { JsonifiedClient } from '@orpc/openapi-client'\nimport { core } from '@h-ai/core'\n\n// ─── 错误码 ───────────────────────────────────────────────────────────────────\n\nconst ApiClientErrorInfo = {\n NETWORK_ERROR: '001:502',\n TIMEOUT: '002:504',\n SERVER_ERROR: '003:502',\n UNAUTHORIZED: '004:401',\n FORBIDDEN: '005:403',\n NOT_FOUND: '006:404',\n VALIDATION_FAILED: '007:400',\n TOKEN_REFRESH_FAILED: '008:401',\n PAYLOAD_TOO_LARGE: '020:413',\n NOT_INITIALIZED: '010:500',\n CONFIG_ERROR: '011:500',\n UNKNOWN: '099:500',\n} as const satisfies ErrorInfo\n\n/**\n * 客户端标准错误集合;包含稳定 code/httpStatus 与构造错误的方法,业务调用先检查 success。\n * 传输失败在 procedure 代理中映射;服务端 HaiResult 的业务错误保持原契约。\n *\n * @example\n * ```ts\n * import { apiClient, HaiApiClientError } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.currentUser()\n * if (!result.success && result.error.code === HaiApiClientError.UNAUTHORIZED.code) {\n * // 由应用切换到登录页面;不输出 Token。\n * const message = result.error.message\n * }\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport const HaiApiClientError = core.error.buildHaiErrorsDef('api-client', ApiClientErrorInfo)\n\n// ─── Token ────────────────────────────────────────────────────────────────────\n\n/**\n * 短期 access token、可选 refresh token 与有效期;HTTP 头使用 Bearer。\n * Cookie 模式的 refresh token 由服务端管理,不能要求浏览器读取或持久化它。\n *\n * @example\n * ```ts\n * import type { TokenPair } from '@h-ai/api-client'\n * const tokens: TokenPair = { accessToken: 'test-access', expiresIn: 3600, tokenType: 'Bearer' }\n * // 示例为测试凭据;生产 Token 只能来自可信服务端认证响应。\n * ```\n */\nexport interface TokenPair {\n /** 短期访问凭证,请求时作为 `Authorization: Bearer ...`。 */\n readonly accessToken: string\n /** 长期刷新凭证;httpOnly cookie 模式下由服务端管理,响应体中可能不存在。 */\n readonly refreshToken?: string\n /** accessToken 有效期秒数。 */\n readonly expiresIn: number\n /** 固定为 `'Bearer'`。 */\n readonly tokenType: 'Bearer'\n}\n\n/**\n * 异步安全凭据存储契约;null 仅表示无可用凭据,读写故障须拒绝 Promise。\n * 浏览器使用内存 access token/httpOnly refresh Cookie,原生端注入平台安全存储,禁止敏感 Token 放入 localStorage。\n * 已绑定客户端的身份切换通过 client.auth,避免绕过刷新代际与串行写入;部分写入失败须执行清理并报告清理失败。\n *\n * 内置实现:\n * - `apiClient.tokenStorage.httpOnlyCookie()` — **默认**;浏览器端推荐,refresh token 由服务端管理,防 XSS。\n * - `apiClient.tokenStorage.memory()` — Node.js 测试 / SSR(需显式传入)。\n *\n * Capacitor / 小程序场景请由对应模块提供(例如 `@h-ai/capacitor`)。\n *\n * @example\n * ```ts\n * import { apiClient, type TokenStorage } from '@h-ai/api-client'\n * const storage: TokenStorage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * const accessToken = await storage.getAccessToken()\n * await storage.clear()\n * ```\n */\nexport interface TokenStorage {\n /**\n * 读取本适配器可用 access token;无凭据返回 null,存储故障拒绝。\n * @returns Token 或 null;不能把读取失败当成不存在。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * const accessToken = await storage.getAccessToken()\n * ```\n */\n readonly getAccessToken: () => Promise<string | null>\n /**\n * 保存 access token;已绑定客户端应通过 auth.setTokens 维护代际。\n * @param token - 可信服务端认证返回的 access token。\n * @returns 写入完成;底层故障须拒绝。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * await storage.clear()\n * ```\n */\n readonly setAccessToken: (token: string) => Promise<void>\n /**\n * 读取 refresh token 或 Cookie 模式内部哨兵;null 仅表示没有凭据。\n * @returns Token、模式哨兵或 null,不是对 httpOnly Cookie 内容的脚本读取。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * const refreshToken = await storage.getRefreshToken()\n * // 内存适配器初始为空;Cookie 适配器由管理器识别模式哨兵。\n * ```\n */\n readonly getRefreshToken: () => Promise<string | null>\n /**\n * 保存 refresh token;Cookie 实现不通过脚本修改服务端 Cookie。\n * @param token - 服务端返回的刷新凭据;禁止写入 Web Storage。\n * @returns 写入完成;原生安全存储故障须拒绝。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setRefreshToken('test-refresh')\n * await storage.clear()\n * ```\n */\n readonly setRefreshToken: (token: string) => Promise<void>\n /**\n * 清空适配器可管理的凭据;Cookie 实现只能清内存 access token。\n * @returns 清理完成;失败须拒绝,服务端 Cookie/会话另由退出接口撤销。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.clear()\n * const remaining = await storage.getAccessToken()\n * ```\n */\n readonly clear: () => Promise<void>\n}\n\n// ─── 配置 ─────────────────────────────────────────────────────────────────────\n\n/**\n * 启用 Token 注入与自动刷新;默认 Cookie 适配器,refreshPath 相对 baseUrl。\n * 网络/超时/5xx 为暂时失败,保留凭据;明确失效才清理并通知。回调不应抛出或记录 Token。\n *\n * @example\n * ```ts\n * import { apiClient, type AuthConfig } from '@h-ai/api-client'\n * const auth: AuthConfig = { storage: apiClient.tokenStorage.memory(), refreshPath: '/auth/refresh', refreshTimeoutMs: 5000, onTokenRefreshed: tokens => { const lifetime = tokens.expiresIn } }\n * // Node/SSR 选择独立内存存储;浏览器通常直接配置 auth: {}。\n * ```\n */\nexport interface AuthConfig {\n /**\n * Token 存储适配器;默认 `apiClient.tokenStorage.httpOnlyCookie()`(refresh token 由服务端管理,防 XSS)。\n *\n * - SSR / Node.js 测试场景请显式传入 `apiClient.tokenStorage.memory()`。\n * - 浏览器刷新后通过 httpOnly Cookie 恢复会话,不将 Token 持久化到 Web Storage。\n * - Capacitor / 小程序请使用对应平台的安全存储适配器(如 `@h-ai/capacitor`)。\n */\n readonly storage?: TokenStorage\n /** 刷新接口路径,相对于 `baseUrl`,默认 `/auth/refresh`。 */\n readonly refreshPath?: string\n /**\n * 刷新请求超时(ms),默认 10_000。\n * 避免 refresh 接口上游挂死时所有 dedup 调用方一起 hang。\n * 超时后按现有 transient 失败处理(不清 token,下次请求可重试)。\n */\n readonly refreshTimeoutMs?: number\n /** 刷新成功回调,用于同步状态(例如多 Tab 广播)。 */\n readonly onTokenRefreshed?: (tokens: TokenPair) => void\n /** 刷新失败回调(如 refresh token 已失效),通常用于跳转登录页。 */\n readonly onRefreshFailed?: () => void\n}\n\n/**\n * 客户端初始化配置;baseUrl 包含 API 前缀,headers/fetch 用于平台适配。\n * auth 缺省时不创建 TokenManager;timeout 是底层请求中断期限,不代表服务端已取消副作用。\n * transport 需要已初始化 crypto,首次业务请求前协商会话;客户端不接收服务端私钥。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientConfig } from '@h-ai/api-client'\n * const config: ApiClientConfig = { baseUrl: 'https://api.example.com/api/v1', auth: { storage: apiClient.tokenStorage.memory() }, timeout: 15000, clientName: 'orders-web', headers: () => ({ 'x-app-channel': 'web' }) }\n * const initialized = await apiClient.init(config)\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * ```\n */\nexport interface ApiClientConfig {\n /** API service 完整 baseUrl,通常含 `apiPrefix`,例如 `https://api.example.com/api/v1`。 */\n readonly baseUrl: string\n /** 认证配置;不传则不启用 Token 注入与自动刷新。 */\n readonly auth?: AuthConfig\n /** 请求超时(ms),默认 30000。 */\n readonly timeout?: number\n /** 客户端标识,写入 `x-request-client` 请求头,默认 `hai-api-client`。 */\n readonly clientName?: string\n /** 额外请求头,可为静态对象或异步函数。 */\n readonly headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)\n /** 自定义 fetch(测试 mock / Capacitor / 小程序桥接)。 */\n readonly fetch?: typeof globalThis.fetch\n /**\n * 启用端到端传输加密(opt-in)。\n *\n * 配置后所有出站请求会在底层 fetch 之上做一次混合加密,并自动解密\n * 标记为 `X-Encrypted: true` 的响应。服务端需启用对应的 `serv.createApp({ transport })`。\n *\n * `crypto` 字段结构与 `@h-ai/crypto` 的 `crypto` 实例兼容;通常直接传入:\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * import { crypto } from '@h-ai/crypto'\n *\n * const cryptography = await crypto.init()\n * if (!cryptography.success) throw new Error(cryptography.error.message)\n * try {\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', transport: { crypto } })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * }\n * finally { await crypto.close() }\n * ```\n */\n readonly transport?: ApiClientTransportConfig\n}\n\n// ─── 传输加密 ────────────────────────────────────────────────────────────────\n\n/**\n * 可选加密会话配置;密钥协商路径相对 baseUrl,并与服务端配置匹配。\n * crypto 必须先初始化;服务器版本公钥通过受保护的 TLS 握手取得,不配置服务端私钥。\n * 传输防重放不能代替业务幂等性,失败后不要自动重放非幂等操作。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientTransportConfig } from '@h-ai/api-client'\n * import { crypto } from '@h-ai/crypto'\n * const cryptography = await crypto.init()\n * if (!cryptography.success) throw new Error(cryptography.error.message)\n * try {\n * const transport: ApiClientTransportConfig = { crypto, keyExchangePath: '/_hai/key-exchange' }\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', transport })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * }\n * finally { await crypto.close() }\n * ```\n */\nexport interface ApiClientTransportConfig {\n /** 可选本地明文 UTF-8 字节上限;缺省自动跟随服务端,指定时取较小值。 */\n readonly maxPlaintextBytes?: number\n /**\n * crypto 服务实例(通常直接传入 `@h-ai/crypto` 的 `crypto`)。\n * api-client 通过 `crypto.transport.createClient(...)` 创建会话。\n */\n readonly crypto: CryptoFunctions\n /**\n * 密钥协商端点路径,相对于 `baseUrl`,默认 `/_hai/key-exchange`。\n *\n * 必须与服务端 `serv.createApp({ transport: { keyExchangePath } })` 一致。\n */\n readonly keyExchangePath?: string\n}\n\n// ─── Client 接口 ──────────────────────────────────────────────────────────────\n\n/**\n * 当前客户端的凭据辅助操作;仅 init 启用 auth 时建立实际管理器。\n * setTokens/clear 撤销旧刷新代际并等待串行存储变更;故障拒绝 Promise,调用方处理失败。\n * clear 不能撤销服务端会话或删除 httpOnly Cookie,退出仍需服务端接口成功。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientAuth } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const auth: ApiClientAuth = apiClient.auth\n * const off = auth.onTokenRefreshed(tokens => { const lifetime = tokens.expiresIn })\n * off()\n * await auth.clear()\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport interface ApiClientAuth {\n /**\n * 替换整对凭据并同步废弃旧刷新代际;缺省 refreshToken 时清除旧值。\n * @param tokens - 服务端成功认证返回的真实 TokenPair。\n * @returns 写入完成;安全存储失败拒绝 Promise,调用方应清理部分凭据并报告清理失败。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const form = { identifier: 'alice', password: 'test-password' }\n * const login = await apiClient.iam.auth.login(form)\n * if (!login.success) throw new Error(login.error.message)\n * try { await apiClient.auth.setTokens(login.data.tokens) }\n * catch (writeError) {\n * try { await apiClient.auth.clear() }\n * catch (cleanupError) { throw new AggregateError([writeError, cleanupError], 'Credential persistence and cleanup failed') }\n * throw writeError\n * }\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly setTokens: (tokens: TokenPair) => Promise<void>\n /**\n * 同步废弃旧刷新代际并等待存储清理;失败拒绝 Promise,不声称已持久删除。\n * 服务端会话与 httpOnly Cookie 仍需服务端退出撤销;本操作不能替代退出接口。\n * @returns 本地清理完成,不返回 HaiResult。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const logout = await apiClient.iam.auth.logout({})\n * await apiClient.auth.clear()\n * if (!logout.success) throw new Error(logout.error.message)\n * // 只有服务端退出和本地清理都成功,才能确认两侧均已处理。\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly clear: () => Promise<void>\n /**\n * 订阅当前管理器刷新成功事件;回调在写入后同步调用,避免抛错或输出 Token。\n * @param callback - 接收 TokenPair 的同步回调。\n * @returns 取消订阅函数;未启用 auth 时返回空操作。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const off = apiClient.auth.onTokenRefreshed(tokens => { const expiresIn = tokens.expiresIn })\n * off()\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly onTokenRefreshed: (callback: (tokens: TokenPair) => void) => () => void\n}\n\n/**\n * 客户端生命周期与认证辅助能力;init 的 HaiResult 必须先检查 success。\n * 同一实例并发 init 共享在途初始化,已初始化后再 init 返回 CONFIG_ERROR;切换配置先 close。\n * config 为脱敏快照;close 清理运行状态/加密会话,不等于服务端退出或凭据存储清理。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientLifecycle } from '@h-ai/api-client'\n * const client: ApiClientLifecycle = apiClient\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const active = client.isInitialized\n * const snapshot = client.config\n * await client.close()\n * ```\n */\nexport interface ApiClientLifecycle {\n /**\n * 观察一次逻辑调用的最终结果;刷新重试保留关联 ID,不包含请求正文或凭据。\n * close 清理订阅;回调异常不影响请求,返回函数可提前取消。\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const unsubscribe = apiClient.observeRequests(record => { const requestId = record.requestId })\n * unsubscribe()\n * ```\n */\n readonly observeRequests: (observer: ApiRequestObserver) => () => void\n /**\n * 构造并提交客户端运行状态;初始化失败返回 CONFIG_ERROR,未提交半初始化对象。\n * 并发 init 共享同一初始化;已初始化再调用会失败,切换配置先 close。\n * @param config - baseUrl、认证、fetch 与可选加密配置。\n * @returns HaiResult;必须先判断 success,再发起 procedure。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.publicConfig()\n * if (!result.success) throw new Error(result.error.message)\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly init: (config: ApiClientConfig) => Promise<HaiResult<void>>\n /**\n * 销毁当前加密会话并清除运行状态,允许重新 init;不发送服务端退出、不清空外置存储。\n * @returns 资源清理完成;不是 HaiResult,不应读取 success。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * const reinitialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!reinitialized.success) throw new Error(reinitialized.error.message)\n * await apiClient.close()\n * ```\n */\n readonly close: () => Promise<void>\n /** 当前脱敏后的运行配置快照;未初始化时为 `null`。 */\n readonly config: ApiClientConfig | null\n /** 是否已完成初始化。 */\n readonly isInitialized: boolean\n /** Token 辅助操作。 */\n readonly auth: ApiClientAuth\n}\n\n/**\n * 逻辑请求诊断摘要。HTTP 200 的失败 HaiResult 同样 success=false。\n * @example\n * ```ts\n * import type { ApiRequestRecord } from '@h-ai/api-client'\n * const record: ApiRequestRecord = { requestId: 'request-1', procedure: 'orders.get', timestamp: 1, duration: 10, success: false, attempts: 1, errorCode: 'orders:missing' }\n * ```\n */\nexport interface ApiRequestRecord {\n readonly requestId: string\n readonly procedure: string\n readonly timestamp: number\n readonly duration: number\n readonly success: boolean\n readonly attempts: number\n readonly errorCode?: string\n readonly method?: string\n readonly url?: string\n readonly status?: number\n}\n\n/**\n * 同步观察者,只做有界收集。\n * @example\n * ```ts\n * import { apiClient, type ApiRequestObserver } from '@h-ai/api-client'\n * const observer: ApiRequestObserver = record => { const failed = !record.success }\n * const unsubscribe = apiClient.observeRequests(observer)\n * unsubscribe()\n * ```\n */\nexport type ApiRequestObserver = (record: ApiRequestRecord) => void\n\n/**\n * 由 oRPC 契约推导的 JSON typed client,与生命周期组合;procedure 的输入/输出由契约决定。\n * 调用先检查初始化和业务 HaiResult;自定义契约不能覆盖 init/close/config/isInitialized/auth 顶层成员。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClient } from '@h-ai/api-client'\n * import { apiContract } from '@h-ai/api-contract'\n * const contract = apiContract.create({ iam: apiContract.iam })\n * const client: ApiClient<typeof contract> = apiClient.create(contract)\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await client.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * const userId = result.data.id\n * }\n * finally { await client.close() }\n * ```\n */\nexport type ApiClient<TContract extends AnyContractRouter>\n = ApiClientLifecycle & JsonifiedClient<ContractRouterClient<TContract>>\n","/**\n * @h-ai/api-client — 主实现\n *\n * 包含两部分:\n * - `createApiClient(contract)`:根据 oRPC contract 创建 typed client 工厂(内部实现)。\n * - `apiClient`:统一公开入口;自身就是默认单例,同时挂载 `create` / `tokenStorage`。\n *\n * Client 同时具备生命周期能力(init/close/config/isInitialized/auth)和 contract\n * 推导出的 procedure 嵌套调用能力(如 `client.iam.auth.login(input)`)。\n *\n * 内置能力:\n * - 401 自动 refresh + 单次重试\n * - 并发 refresh 共享 Promise\n * - 请求超时(AbortController)\n * - `ORPCError` → `HaiApiClientError` 标准化映射\n * - 未初始化时调用 procedure 返回 `NOT_INITIALIZED`,不抛异常\n *\n * @example 默认单例\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const me = await apiClient.iam.auth.currentUser()\n * if (!me.success) throw new Error(me.error.message)\n * const userId = me.data.id\n * }\n * finally { await apiClient.close() }\n * ```\n *\n * @example 自定义 contract\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * import { apiContract } from '@h-ai/api-contract'\n *\n * const client = apiClient.create(apiContract.create({ iam: apiContract.iam }))\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await client.iam.auth.publicConfig()\n * if (!result.success) throw new Error(result.error.message)\n * }\n * finally { await client.close() }\n * ```\n *\n * @module api-client-main\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { TransportClient } from '@h-ai/crypto'\nimport type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'\nimport type { JsonifiedClient } from '@orpc/openapi-client'\nimport type { TokenManager } from './api-client-auth.js'\nimport type {\n ApiClient,\n ApiClientAuth,\n ApiClientConfig,\n ApiClientLifecycle,\n ApiRequestObserver,\n ApiRequestRecord,\n} from './api-client-types.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, ok } from '@h-ai/core'\nimport { TransportBodyTooLargeError, TransportTimeError } from '@h-ai/crypto'\nimport { createORPCClient, ORPCError } from '@orpc/client'\nimport { OpenAPILink } from '@orpc/openapi-client/fetch'\nimport {\n createHttpOnlyCookieTokenStorage,\n createMemoryTokenStorage,\n createTokenManager,\n} from './api-client-auth.js'\nimport { apiClientM } from './api-client-i18n.js'\nimport { createIamAuthActions } from './api-client-iam.js'\nimport { HaiApiClientError } from './api-client-types.js'\n\nconst DEFAULT_TIMEOUT = 30_000\nconst DEFAULT_CLIENT_NAME = 'hai-api-client'\nconst DEFAULT_REFRESH_PATH = apiContract.pathOf(apiContract.iam.auth.refresh)\nconst AUTH_ENTRY_PATHS = [\n apiContract.pathOf(apiContract.iam.auth.login),\n apiContract.pathOf(apiContract.iam.auth.loginWithOtp),\n apiContract.pathOf(apiContract.iam.auth.loginWithLdap),\n apiContract.pathOf(apiContract.iam.auth.requestPasswordReset),\n apiContract.pathOf(apiContract.iam.auth.confirmPasswordReset),\n apiContract.pathOf(apiContract.iam.auth.register),\n]\nconst TRAILING_SLASHES_REGEX = /\\/+$/\nconst REQUEST_OBSERVATION = Symbol('hai.api-client.request-observation')\n/**\n * 内部构造的可变摘要,不对消费者暴露写入能力。\n * @example 模块内部上下文\n * ```ts\n * function update(record: MutableRequestRecord) { record.attempts += 1 }\n * ```\n */\ntype MutableRequestRecord = { -readonly [Key in keyof ApiRequestRecord]: ApiRequestRecord[Key] }\n\n/** Token 存储方案集合:内存 / httpOnly Cookie。 */\nconst tokenStorage = {\n /** 内存存储——SSR / Node 测试或一次性会话使用。 */\n memory: createMemoryTokenStorage,\n /** httpOnly Cookie 存储——浏览器场景推荐方案。 */\n httpOnlyCookie: createHttpOnlyCookieTokenStorage,\n} as const\n\n// ─── 工厂 ─────────────────────────────────────────────────────────────────────\n\n/**\n * 按真实 oRPC 契约创建独立 typed client;公开调用入口为 apiClient.create(contract)。\n * init 返回 HaiResult,成功后才调用 procedure;已初始化实例必须先 close 再换配置。\n * procedure 将传输异常转换为 HaiResult;auth.setTokens/clear 的安全存储失败仍会拒绝 Promise。\n * SSR 应为每个请求创建客户端和内存存储;不共享默认单例中的用户凭据。\n * @param contract - 已定义的嵌套 oRPC contract;顶层命名不能与生命周期成员冲突。\n * @returns 生命周期、认证辅助操作与契约推导出的 procedure 代理。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * import { apiContract } from '@h-ai/api-contract'\n * const client = apiClient.create(apiContract.create({ iam: apiContract.iam }))\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: { storage: apiClient.tokenStorage.memory() }, timeout: 10000 })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await client.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * const userId = result.data.id\n * }\n * finally { await client.close() }\n * ```\n */\nexport function createApiClient<const TContract extends AnyContractRouter>(\n contract: TContract,\n): ApiClient<TContract> {\n // 客户端运行时状态\n const state: {\n config: ApiClientConfig | null\n rawClient: JsonifiedClient<ContractRouterClient<TContract>> | null\n tokenManager: TokenManager | undefined\n transport: TransportClient | undefined\n observers: Set<ApiRequestObserver>\n } = {\n config: null,\n rawClient: null,\n tokenManager: undefined,\n transport: undefined,\n observers: new Set(),\n }\n\n const auth: ApiClientAuth = {\n async setTokens(tokens) { await state.tokenManager?.setTokens(tokens) },\n async clear() { await state.tokenManager?.clear() },\n onTokenRefreshed(callback) {\n return state.tokenManager?.onTokenRefreshed(callback) ?? (() => {})\n },\n }\n\n let initPromise: Promise<HaiResult<void>> | null = null\n const lifecycle: ApiClientLifecycle = {\n async init(config) {\n // 并发重入防护:同时多次 init() 只执行一次。\n // 另一个调用方 await 同一个 in-flight Promise,避免重复创建 raw client / token manager。\n if (initPromise)\n return initPromise\n // 已初始化后必须先 close() 再重新 init,避免旧 transport 会话被新状态覆盖后无法 destroy。\n if (state.rawClient) {\n return err(\n HaiApiClientError.CONFIG_ERROR,\n apiClientM('apiClient_alreadyInitialized'),\n )\n }\n initPromise = (async (): Promise<HaiResult<void>> => {\n let nextTransport: TransportClient | undefined\n try {\n nextTransport = buildTransportSession(config)\n const nextTokenManager = buildTokenManager(config, nextTransport)\n const nextRawClient = buildRawClient(contract, config, nextTokenManager, nextTransport)\n\n // 所有可能失败的构建步骤完成后再提交状态,避免调用方看到半初始化 client。\n state.config = config\n state.transport = nextTransport\n state.tokenManager = nextTokenManager\n state.rawClient = nextRawClient\n return ok(undefined)\n }\n catch (error) {\n // 构建过程中如果 transport 已创建但后续失败,必须主动释放,避免会话/定时器泄漏。\n nextTransport?.destroy()\n state.config = null\n state.rawClient = null\n state.tokenManager = undefined\n state.transport = undefined\n return err(\n HaiApiClientError.CONFIG_ERROR,\n apiClientM('apiClient_configError', { params: { error: String(error) } }),\n error,\n )\n }\n })()\n try {\n return await initPromise\n }\n finally {\n initPromise = null\n }\n },\n async close() {\n state.observers.clear()\n state.transport?.destroy()\n state.config = null\n state.rawClient = null\n state.tokenManager = undefined\n state.transport = undefined\n },\n get config() { return state.config ? core.sanitize.sanitizeSensitiveFields(state.config) : null },\n get isInitialized() { return state.rawClient !== null },\n auth,\n observeRequests(observer) {\n state.observers.add(observer)\n return () => {\n state.observers.delete(observer)\n }\n },\n }\n\n // 生命周期成员优先,其余属性只记录真实契约路径,末端调用才交给 rawClient。\n // 契约顶层不能与 init/close/config/isInitialized/auth 重名;此处不提供额外路由注册。\n return new Proxy({}, {\n get(_target, property) {\n // Proxy 的属性键不一定是 string,也可能是 Symbol(例如某些运行时内部探测)。\n // 本 client 只处理字符串路径;非字符串键直接忽略,避免把 Symbol 误当成 API 路径的一部分。\n if (typeof property !== 'string')\n return undefined\n\n // 顶层字段命中 lifecycle 时,直接返回真实成员。\n // 例如:\n // - property === 'init' → 返回 init 函数\n // - property === 'auth' → 返回 auth 对象\n // 这样 `client.init()` / `client.auth.clear()` 就能像普通对象一样工作。\n if (property in lifecycle) {\n return Reflect.get(lifecycle, property)\n }\n\n // 走到这里,说明它不是生命周期字段,那就把它当成 contract 路径的第一段。\n // 例如访问 `client.iam` 时,这里会创建一个 path = ['iam'] 的嵌套 Proxy。\n // 后续如果继续访问 `.auth.login`,path 会逐步变成:\n // ['iam', 'auth'] → ['iam', 'auth', 'login']\n // 最终在函数调用时再映射到真正的 rawClient.iam.auth.login(...args)。\n return createProcedureProxy(state, [property])\n },\n // 这里的返回值在运行时是一个 Proxy,但从“可用能力”上看,它符合 `ApiClient<TContract>`:\n // - 一部分成员来自 lifecycle\n // - 另一部分成员来自 contract 推导出的 procedure 层级\n // 因此使用类型断言把这个动态对象声明为最终对外的 typed client。\n }) as ApiClient<TContract>\n}\n\n// ─── 默认单例 ─────────────────────────────────────────────────────────────────\n\n/**\n * 默认 API typed client:绑定 `iam` / `storage` / `ai` 三个领域。\n *\n * 大多数应用只需 `import { apiClient }` 并调用 `apiClient.init()` 即可。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const initialized = await apiClient.init({\n * baseUrl: 'http://localhost:3000/api/v1',\n * auth: { refreshPath: '/auth/refresh' },\n * })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * }\n * finally { await apiClient.close() }\n * ```\n */\nconst defaultApiContract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })\n\n/**\n * 默认 iam/storage/ai typed client 与 create、tokenStorage、createAuthActions 命名空间的交叉类型。\n * 具体项目契约使用 apiClient.create 的返回类型,不给默认单例附加未知 procedure。\n *\n * @example\n * ```ts\n * import { apiClient, type DefaultApiClient } from '@h-ai/api-client'\n * const client: DefaultApiClient = apiClient\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try { const actions = client.createAuthActions(client); const result = await actions.publicConfig(); if (!result.success) throw new Error(result.error.message) }\n * finally { await client.close() }\n * ```\n */\nexport type DefaultApiClient = ApiClient<typeof defaultApiContract> & {\n readonly create: typeof createApiClient\n readonly tokenStorage: typeof tokenStorage\n readonly createAuthActions: typeof createIamAuthActions\n}\n\n/**\n * API Client 公开入口:默认 iam/storage/ai 单例,以及独立实例、安全存储和认证动作工厂。\n * 未启用 auth 时不注入或刷新 Token;浏览器 auth: {} 使用 httpOnly Cookie 方案。\n * close 释放客户端运行状态/加密会话,不能替代服务端退出或 auth.clear。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * const permissions = result.data.permissions\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport const apiClient = new Proxy(createApiClient(defaultApiContract) as ApiClient<typeof defaultApiContract>, {\n get(target, property, receiver) {\n if (property === 'create')\n return createApiClient\n if (property === 'tokenStorage')\n return tokenStorage\n if (property === 'createAuthActions')\n return createIamAuthActions\n return Reflect.get(target as object, property, receiver)\n },\n}) as DefaultApiClient\n\n// ─── 内部:procedure 代理 ─────────────────────────────────────────────────────\n\n/**\n * 构造只记录属性路径的内部代理,末端调用交给 callProcedure;不在访问属性时发送请求。\n * @param state - 当前 rawClient,可在 close 后变为 null。\n * @param state.rawClient - 当前 oRPC 客户端;未初始化或关闭后为 null。\n * @param state.observers - 本实例逻辑请求订阅集合。\n * @param path - 已访问的 contract 属性路径。\n * @returns 动态代理;由契约公开类型约束调用路径,不对外承诺任意字符串 API。\n *\n * @example 模块内部上下文\n * ```ts\n * const proxy = createProcedureProxy({ rawClient: null }, ['iam'])\n * // 直到 apply 才调用 callProcedure;未初始化返回失败 HaiResult。\n * ```\n */\nfunction createProcedureProxy<TContract extends AnyContractRouter>(\n state: { rawClient: JsonifiedClient<ContractRouterClient<TContract>> | null, observers?: Set<ApiRequestObserver> },\n path: string[],\n): unknown {\n return new Proxy(() => undefined, {\n get(_t, property) {\n if (typeof property !== 'string')\n return undefined\n return createProcedureProxy(state, [...path, property])\n },\n apply(_t, _thisArg, args) {\n return callObservedProcedure(state.rawClient, path, args, state.observers)\n },\n })\n}\n\n/** 不复制业务入参;通过 oRPC 每次调用的 context 将同一关联传给传输层。 */\nasync function callObservedProcedure(rawClient: unknown, path: string[], args: unknown[], observers?: ReadonlySet<ApiRequestObserver>): Promise<unknown> {\n const record: MutableRequestRecord = { requestId: core.context.get().requestId ?? core.id.uuid(), procedure: path.join('.'), timestamp: Date.now(), duration: 0, success: false, attempts: 0 }\n const provided = args[1] && typeof args[1] === 'object' ? args[1] as Record<string, unknown> : {}\n const providedContext = provided.context && typeof provided.context === 'object' ? provided.context : {}\n const options = { ...provided, context: { ...providedContext, [REQUEST_OBSERVATION]: record } }\n const result = await callProcedure(rawClient, path, [args[0], options])\n record.duration = Math.max(0, Date.now() - record.timestamp)\n record.success = !(result && typeof result === 'object' && 'success' in result && result.success === false)\n if (result && typeof result === 'object' && 'error' in result && result.error && typeof result.error === 'object' && 'code' in result.error)\n record.errorCode = String(result.error.code)\n for (const observer of observers ?? []) {\n try {\n const observed: unknown = observer(Object.freeze({ ...record }))\n if (observed instanceof Promise)\n void observed.catch(() => {})\n }\n catch { /* 观察失败不改变请求结果。 */ }\n }\n return result\n}\n\n/**\n * 解析真实 rawClient 中的 procedure 并调用,将调用异常映射为标准 HaiResult 失败。\n * @param rawClient - 已构建的 oRPC client;缺省时返回 NOT_INITIALIZED。\n * @param path - 契约层级路径。\n * @param args - 原始 procedure 参数。\n * @returns 原契约结果或客户端失败,不把失败改成成功。\n *\n * @example 模块内部上下文\n * ```ts\n * const result = await callProcedure(null, ['iam', 'auth', 'currentUser'], [])\n * // 实际得到 NOT_INITIALIZED;返回值为 unknown,外部类型来自真实契约。\n * ```\n */\nasync function callProcedure(\n rawClient: unknown,\n path: string[],\n args: unknown[],\n): Promise<unknown> {\n if (!rawClient) {\n return err(HaiApiClientError.NOT_INITIALIZED, apiClientM('apiClient_notInitialized'))\n }\n\n // 沿 path 解析 rawClient[a][b][c]...\n let target: unknown = rawClient\n for (const segment of path) {\n if ((typeof target !== 'object' || target === null) && typeof target !== 'function') {\n return err(HaiApiClientError.UNKNOWN, apiClientM('apiClient_unknown'))\n }\n target = Reflect.get(target as object, segment)\n }\n if (typeof target !== 'function') {\n return err(HaiApiClientError.UNKNOWN, apiClientM('apiClient_unknown'))\n }\n\n try {\n return await (target as (...a: unknown[]) => Promise<unknown>)(...args)\n }\n catch (error) {\n return mapClientError(error)\n }\n}\n\n// ─── 内部:构建 raw client 与 token manager ──────────────────────────────────\n\n/**\n * 仅 auth 存在时创建 TokenManager;刷新沿用自定义头、客户端标识与同一加密 fetch。\n * @param config - 已选定的客户端配置。\n * @param transport - 可选已创建的加密会话。\n * @returns 本实例管理器;没有 auth 时为 undefined,不自行启用认证。\n *\n * @example 模块内部上下文\n * ```ts\n * const manager = buildTokenManager({ baseUrl: 'https://api.example.com/api/v1', auth: { storage: createMemoryTokenStorage() }, headers: { 'x-app-channel': 'web' } }, undefined)\n * await manager?.clear()\n * ```\n */\nfunction buildTokenManager(config: ApiClientConfig, transport: TransportClient | undefined): TokenManager | undefined {\n if (!config.auth)\n return undefined\n\n const fetchFn = transport?.encryptedFetch ?? config.fetch ?? globalThis.fetch.bind(globalThis)\n // 默认使用 httpOnly cookie 存储(推荐方案:refresh token 由服务端管理,防 XSS)。\n // SSR / Node.js 测试场景请显式传入 `apiClient.tokenStorage.memory()`。\n // 原生客户端显式传入平台安全存储,浏览器不持久化 Token 到 Web Storage。\n const storage = config.auth.storage ?? createHttpOnlyCookieTokenStorage()\n const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, '')\n const refreshPath = config.auth.refreshPath ?? DEFAULT_REFRESH_PATH\n const refreshUrl = `${baseUrl}${refreshPath.startsWith('/') ? refreshPath : `/${refreshPath}`}`\n\n // 刷新沿用客户端标识与自定义头(包括原生 body 通道标识),并复用同一加密 fetch。\n const refreshFetch: typeof fetch = async (input, init) => {\n const configured = typeof config.headers === 'function' ? await config.headers() : config.headers ?? {}\n const headers = new Headers(configured)\n if (!headers.has('accept-language'))\n headers.set('accept-language', core.i18n.getGlobalLocale())\n headers.set('x-request-client', config.clientName ?? DEFAULT_CLIENT_NAME)\n new Headers(init?.headers).forEach((value, name) => headers.set(name, value))\n return fetchFn(input, { ...init, headers })\n }\n const manager = createTokenManager(storage, refreshUrl, refreshFetch, config.auth.onRefreshFailed, config.auth.refreshTimeoutMs)\n if (config.auth.onTokenRefreshed) {\n manager.onTokenRefreshed(config.auth.onTokenRefreshed)\n }\n return manager\n}\n\n/**\n * 根据显式 transport 配置创建会话;首次 encryptedFetch 才进行实际密钥协商。\n * @param config - 客户端配置及已初始化 crypto。\n * @returns 新会话或 undefined;调用方在失败/close 时 destroy。\n *\n * @example 模块内部上下文\n * ```ts\n * const session = buildTransportSession({ baseUrl: 'https://api.example.com/api/v1' })\n * // 未配置 transport 时不产生会话;正常明文请求仍由实际 fetch 发送。\n * session?.destroy()\n * ```\n */\nfunction buildTransportSession(config: ApiClientConfig): TransportClient | undefined {\n if (!config.transport)\n return undefined\n const baseFetch = config.fetch ?? globalThis.fetch.bind(globalThis)\n const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, '')\n const path = config.transport.keyExchangePath ?? '/_hai/key-exchange'\n const keyExchangeUrl = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`\n return config.transport.crypto.transport.createClient({ keyExchangeUrl, fetch: baseFetch, maxPlaintextBytes: config.transport.maxPlaintextBytes })\n}\n\n/**\n * 以同一 contract 构造 OpenAPILink 与 JSON typed client,统一头组装、超时和认证重试。\n * @param contract - 实际 procedure 契约。\n * @param config - 目标地址与平台 fetch。\n * @param tokenManager - 可选 Token 管理器。\n * @param transport - 可选加密会话。\n * @returns oRPC JSON client;模块外层代理将调用异常映射为失败 HaiResult。\n *\n * @example 模块内部上下文\n * ```ts\n * const contract = apiContract.create({ iam: apiContract.iam })\n * const raw = buildRawClient(contract, { baseUrl: 'https://api.example.com/api/v1' }, undefined, undefined)\n * // 构造不发送请求;业务调用通过外层 ApiClient 代理。\n * ```\n */\nfunction buildRawClient<TContract extends AnyContractRouter>(\n contract: TContract,\n config: ApiClientConfig,\n tokenManager: TokenManager | undefined,\n transport: TransportClient | undefined,\n): JsonifiedClient<ContractRouterClient<TContract>> {\n const link = new OpenAPILink<Record<PropertyKey, unknown>>(contract, {\n url: config.baseUrl,\n headers: async options => buildHeaders(config, tokenManager, getObservation(options.context)?.requestId),\n fetch: async (request, init, options) => {\n const record = getObservation(options.context)\n if (record) {\n record.method = request.method\n const url = new URL(request.url)\n url.search = ''\n url.hash = ''\n record.url = core.sanitize.sanitizeUrl(url.toString())\n }\n const response = await fetchWithRefresh(request, init, config, tokenManager, transport, record)\n if (record)\n record.status = response.status\n return response\n },\n })\n return createORPCClient<JsonifiedClient<ContractRouterClient<TContract>>>(link)\n}\n\n/** 内部 context 属性只由逻辑调用建立,不能从业务输入读取。 */\nfunction getObservation(context: Record<PropertyKey, unknown>): MutableRequestRecord | undefined {\n const record = context[REQUEST_OBSERVATION]\n return record && typeof record === 'object' && 'requestId' in record ? record as MutableRequestRecord : undefined\n}\n\n/**\n * 合并静态/动态配置头与客户端标识,读取可用 access token 后设置 Bearer 头。\n * @param config - 用户请求头与客户端标识。\n * @param tokenManager - 可选管理器;读取安全存储失败向上游传播。\n * @returns 单次请求的头快照,不改变调用方配置对象。\n *\n * @example 模块内部上下文\n * ```ts\n * const headers = await buildHeaders({ baseUrl: 'https://api.example.com/api/v1', clientName: 'orders-web', headers: () => ({ 'x-app-channel': 'web' }) }, undefined)\n * const channel = headers['x-app-channel']\n * ```\n */\nasync function buildHeaders(\n config: ApiClientConfig,\n tokenManager: TokenManager | undefined,\n requestId?: string,\n): Promise<Record<string, string>> {\n const configured = typeof config.headers === 'function' ? await config.headers() : config.headers ?? {}\n const headers = new Headers(configured)\n if (!headers.has('accept-language'))\n headers.set('accept-language', core.i18n.getGlobalLocale())\n const accessToken = await tokenManager?.storage.getAccessToken()\n return {\n ...Object.fromEntries(headers),\n 'x-request-client': config.clientName ?? DEFAULT_CLIENT_NAME,\n 'x-request-id': requestId ?? core.context.get().requestId ?? core.id.uuid(),\n ...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}),\n }\n}\n\n/**\n * 发送前克隆一次性请求体;HTTP 401 或有效失败包络的 401 才尝试刷新并最多重试一次。\n * 登录/注册/找回密码与 refresh 入口保留原始失败,不递归刷新;重试保留 body 并替换 Bearer 头。\n * @param request - 尚未消费的 Request。\n * @param init - oRPC 提供的重定向选项。\n * @param init.redirect - 可选的 fetch 重定向策略,原样传给首次请求及重试。\n * @param config - 请求与刷新配置。\n * @param tokenManager - 缺省时直接返回首次响应。\n * @param transport - 刷新与重试共用的可选加密会话。\n * @returns 最终 HTTP Response;不保证远端非幂等副作用没有发生。\n *\n * @example 模块内部上下文\n * ```ts\n * const config: ApiClientConfig = { baseUrl: 'https://api.example.com/api/v1', fetch: async () => Response.json({ success: true, data: {} }) }\n * const response = await fetchWithRefresh(new Request('https://api.example.com/api/v1/auth/me'), {}, config, undefined, undefined)\n * const status = response.status\n * ```\n */\nasync function fetchWithRefresh(\n request: Request,\n init: { redirect?: Request['redirect'] },\n config: ApiClientConfig,\n tokenManager: TokenManager | undefined,\n transport: TransportClient | undefined,\n observation?: MutableRequestRecord,\n): Promise<Response> {\n // Fetch Request 的 body 是一次性可读流;为了支持 401 后重发 POST/PUT 等带 body 的请求,\n // 必须在首次发送前 `clone()` 一份。即使没有 401,clone 后的副本也只会被 GC 回收,无副作用。\n const retryRequest = tokenManager ? request.clone() : undefined\n if (observation)\n observation.attempts++\n const response = await fetchWithTimeout(request, init, config, transport)\n if (!tokenManager || !retryRequest)\n return response\n\n // 登录失败必须保留原始错误;refresh 本身也不能再次触发自动刷新。\n const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, '')\n const refreshPath = config.auth?.refreshPath ?? DEFAULT_REFRESH_PATH\n const excludedPaths = [...AUTH_ENTRY_PATHS, refreshPath]\n if (excludedPaths.some(path => new URL(`${baseUrl}${path.startsWith('/') ? path : `/${path}`}`).pathname === new URL(request.url).pathname))\n return response\n if (response.status !== 401 && !await isUnauthorizedResult(response))\n return response\n\n const tokens = await tokenManager.refresh()\n if (!tokens)\n return response\n\n const headers = new Headers(retryRequest.headers)\n headers.set('authorization', `Bearer ${tokens.accessToken}`)\n if (observation)\n observation.attempts++\n return fetchWithTimeout(new Request(retryRequest, { headers }), init, config, transport)\n}\n\n/**\n * 只从 HTTP 200 JSON 的有效失败 HaiResult 识别 httpStatus=401;克隆响应避免消费原正文。\n * @param response - 原始 HTTP 响应。\n * @returns 是否满足认证失效包络;非法 JSON/字段缺失留给原解析路径处理。\n *\n * @example 模块内部上下文\n * ```ts\n * const response = Response.json({ success: false, error: { code: 'hai:iam:001', message: 'Session expired', httpStatus: 401 } })\n * const unauthorized = await isUnauthorizedResult(response)\n * const originalBody = await response.json()\n * // 克隆检查没有消耗原响应正文。\n * ```\n */\nasync function isUnauthorizedResult(response: Response): Promise<boolean> {\n if (response.status !== 200 || !response.headers.get('content-type')?.includes('application/json'))\n return false\n try {\n const body: unknown = await response.clone().json()\n if (!body || typeof body !== 'object' || !('success' in body) || body.success !== false\n || !('error' in body) || !body.error || typeof body.error !== 'object') {\n return false\n }\n return 'httpStatus' in body.error && body.error.httpStatus === 401\n && 'code' in body.error && typeof body.error.code === 'string'\n && 'message' in body.error && typeof body.error.message === 'string'\n }\n catch {\n // 非 JSON/非法包络交由既有客户端解析路径报告,不掩盖为认证失败。\n return false\n }\n}\n\n/**\n * 把请求超时和调用方 signal 连接到本次 fetch;finally 清除本次超时计时器。\n * @param request - 含调用方取消信号的 Request。\n * @param init - oRPC 重定向设置。\n * @param init.redirect - 可选的 fetch 重定向策略,原样交给本次实际 fetch。\n * @param config - 超时与 fetch 配置。\n * @param transport - 可选加密 fetch,优先于原始 fetch。\n * @returns HTTP 响应;超时/取消异常由外层 procedure 映射,不能视为远端回滚。\n *\n * @example 模块内部上下文\n * ```ts\n * const request = new Request('https://api.example.com/api/v1/auth/me')\n * const response = await fetchWithTimeout(request, {}, { baseUrl: 'https://api.example.com/api/v1', timeout: 1000, fetch: async () => new Response(null, { status: 204 }) }, undefined)\n * const status = response.status\n * ```\n */\nasync function fetchWithTimeout(\n request: Request,\n init: { redirect?: Request['redirect'] },\n config: ApiClientConfig,\n transport: TransportClient | undefined,\n): Promise<Response> {\n // 启用传输加密时,用 encryptedFetch 作为实际 fetch;\n // 它会首次请求前自动完成密钥协商,后续请求复用会话。\n const fetchFn = transport?.encryptedFetch ?? config.fetch ?? globalThis.fetch.bind(globalThis)\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n // 保留调用方原始 signal 的中断能力:上游主动 abort 也能控制超时 controller。\n const userSignal = request.signal\n if (userSignal) {\n if (userSignal.aborted) {\n controller.abort()\n }\n else {\n userSignal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n }\n try {\n return await fetchFn(new Request(request, { signal: controller.signal }), init)\n }\n finally {\n clearTimeout(timeoutId)\n }\n}\n\n/**\n * 将 oRPC 状态、AbortError 与其它传输异常映射为稳定客户端错误;保留 oRPC code/status/data 详情。\n * @param error - procedure 抛出的未知异常。\n * @returns 失败 HaiResult,不包含虚构的成功数据。\n *\n * @example 模块内部上下文\n * ```ts\n * const result = mapClientError(new DOMException('Request aborted', 'AbortError'))\n * if (!result.success) { const code = result.error.code }\n * ```\n */\nfunction mapClientError(error: unknown) {\n const time = error instanceof TransportTimeError ? error : error instanceof Error && error.cause instanceof TransportTimeError ? error.cause : undefined\n if (time)\n return err(HaiApiClientError.VALIDATION_FAILED, time.message, { transportCode: time.code })\n const capacity = error instanceof TransportBodyTooLargeError ? error : error instanceof Error && error.cause instanceof TransportBodyTooLargeError ? error.cause : undefined\n if (capacity)\n return err(HaiApiClientError.PAYLOAD_TOO_LARGE, capacity.message, { direction: capacity.direction, stage: capacity.stage, limitBytes: capacity.limitBytes, actualBytes: capacity.actualBytes })\n if (error instanceof ORPCError) {\n // 保留 ORPCError 的原始 code / status / data,供上层区分具体业务异常(不仅是 HTTP 状态码)。\n const detail = { orpcCode: error.code, status: error.status, data: error.data }\n if (error.status === 413)\n return err(HaiApiClientError.PAYLOAD_TOO_LARGE, error.message, detail)\n if (error.status === 400 || error.status === 422)\n return err(HaiApiClientError.VALIDATION_FAILED, error.message, detail)\n if (error.status === 401)\n return err(HaiApiClientError.UNAUTHORIZED, error.message, detail)\n if (error.status === 403)\n return err(HaiApiClientError.FORBIDDEN, error.message, detail)\n if (error.status === 404)\n return err(HaiApiClientError.NOT_FOUND, error.message, detail)\n return err(HaiApiClientError.SERVER_ERROR, error.message, detail)\n }\n if (error instanceof DOMException && error.name === 'AbortError') {\n return err(HaiApiClientError.TIMEOUT, apiClientM('apiClient_timeout'), error)\n }\n return err(HaiApiClientError.NETWORK_ERROR, apiClientM('apiClient_networkError'), error)\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/api-client-auth.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/api-client-i18n.ts","../src/api-client-iam.ts","../src/api-client-types.ts","../src/api-client-main.ts"],"names":["core"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/api-client — Token 存储与刷新管理\n *\n * 提供两个内置 Token 存储适配器(httpOnly Cookie / 内存)和一个 Token 管理器\n * (封装存储读写、自动刷新、并发去重、回调通知)。\n *\n * @example 浏览器 SPA\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const initialized = await apiClient.init({\n * baseUrl: 'https://api.example.com/api/v1',\n * auth: { storage: apiClient.tokenStorage.httpOnlyCookie() },\n * })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * ```\n *\n * @example Node.js 测试\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * await storage.clear()\n * ```\n *\n * @module api-client-auth\n */\n\nimport type { TokenPair, TokenStorage } from './api-client-types.js'\nimport { core } from '@h-ai/core'\nimport { z } from 'zod'\n\nconst logger = core.logger.child({ module: 'api-client', scope: 'auth' })\n\n/**\n * httpOnly cookie 模式哨兵值。\n * `apiClient.tokenStorage.httpOnlyCookie()` 的 `getRefreshToken()` 返回此值,\n * 告知 `doRefresh` 应依赖浏览器自动发送的 httpOnly Cookie,\n * 而不是将 refreshToken 写入请求体。\n */\nconst HTTPONLY_COOKIE_SENTINEL = '__httponly_cookie__'\n\n// ─── 内存存储 ─────────────────────────────────────────────────────────────────\n\n/**\n * 创建仅属于本适配器实例的内存凭据存储,页面刷新或进程结束后丢失。\n * SSR 应为每个请求创建客户端和存储实例,避免不同用户共享默认单例。\n * 通过 client.auth 更新已绑定的存储,才能撤销旧代际刷新;直接存储写入不提供这种保证。\n * @returns 两个初始为空的 Token 槽位及清理操作。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * const saved = await storage.getAccessToken()\n * await storage.clear()\n * // saved 为本实例的测试值;不把 Token 写入 Web Storage。\n * ```\n */\nexport function createMemoryTokenStorage(): TokenStorage {\n let accessToken: string | null = null\n let refreshToken: string | null = null\n return {\n async getAccessToken() { return accessToken },\n async setAccessToken(token) { accessToken = token },\n async getRefreshToken() { return refreshToken },\n async setRefreshToken(token) { refreshToken = token },\n async clear() {\n accessToken = null\n refreshToken = null\n },\n }\n}\n\n// ─── httpOnly Cookie 存储 ────────────────────────────────────────────────────\n\n/**\n * 创建浏览器使用的内存 access token 与服务端 httpOnly refresh Cookie 适配器。\n * refresh getter 返回内部哨兵,setter 不修改 Cookie;clear 只清除内存 access token。\n * 刷新请求使用 credentials: include;服务端须实际设置/撤销 Cookie,配置适当的 Secure、SameSite、Path 与跨域凭据策略。\n * HttpOnly 限制脚本直接读取 Cookie,不能阻止已执行的恶意脚本代用户发请求;服务端仍须授权和防护 CSRF。\n * @returns 独立内存 access token 与 Cookie 模式标记;不接触 localStorage。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: { storage: apiClient.tokenStorage.httpOnlyCookie() } })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const me = await apiClient.iam.auth.currentUser()\n * if (!me.success) throw new Error(me.error.message)\n * const userId = me.data.id\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport function createHttpOnlyCookieTokenStorage(): TokenStorage {\n // access token 保存在内存中,用于注入 Authorization 请求头\n let memoryAccessToken: string | null = null\n return {\n async getAccessToken() { return memoryAccessToken },\n async setAccessToken(token) { memoryAccessToken = token },\n // refresh token 由服务端通过 Set-Cookie 设置为 httpOnly cookie;\n // JS 不可见,返回哨兵值让 TokenManager 识别此模式。\n async getRefreshToken() { return HTTPONLY_COOKIE_SENTINEL },\n // refresh token 由服务端管理,JS 端无法写入 httpOnly cookie,忽略此调用。\n async setRefreshToken(_token) { /* 由服务端 Set-Cookie 响应头管理,客户端无需操作 */ },\n async clear() {\n memoryAccessToken = null\n // httpOnly cookie 的清除必须由服务端 logout 端点完成(Set-Cookie: Max-Age=0);\n // 客户端无法通过 JS 直接删除 httpOnly cookie。\n },\n }\n}\n\n// ─── Token 管理器 ─────────────────────────────────────────────────────────────\n\n/** 校验 refresh 响应体中的 Token 字段;httpOnly cookie 模式允许不返回 refreshToken。 */\nconst RefreshTokenPayloadSchema = z.object({\n accessToken: z.string().min(1),\n refreshToken: z.string().min(1).optional(),\n expiresIn: z.number().optional(),\n tokenType: z.string().optional(),\n})\n\n/**\n * 当前代际刷新成功后的同步通知;参数包含敏感凭据,不应记录 Token。\n * 回调在持久写入成功后调用,应自行处理业务异常,不能把抛错当成刷新重试机制。\n *\n * @example 模块内部上下文\n * ```ts\n * const observed: number[] = []\n * const callback: RefreshCallback = tokens => { observed.push(tokens.expiresIn) }\n * callback({ accessToken: 'test-access', expiresIn: 3600, tokenType: 'Bearer' })\n * ```\n */\ntype RefreshCallback = (tokens: TokenPair) => void\n\n/**\n * 创建模块内部 Token 管理器;包用户通过 apiClient.auth 使用,包入口不导出此工厂。\n * 同一代际的并发刷新共享请求;clear/setTokens 同步撤销旧代际,所有存储变更串行提交。\n * 迟到响应不能覆盖新账号、恢复退出凭据或触发旧回调;已开始的存储写入须完成后才能执行后续身份变更。\n * 4xx、无效响应和非 Cookie 模式缺少 refreshToken 会尝试清理凭据;5xx、网络/超时保留凭据并返回 null。\n * 无刷新凭据时通知失败并返回 null;存储读取失败可拒绝 Promise,setTokens/clear 的存储失败向调用方传播。\n * @param storage - 独占的安全存储适配器。\n * @param refreshEndpointUrl - baseUrl 与 refreshPath 组合后的完整 URL。\n * @param fetchFn - 实际 fetch;Cookie 模式发送凭据,普通模式发送 JSON refreshToken。\n * @param onRefreshFailed - 当前代际明确失效后的同步通知。\n * @param refreshTimeoutMs - fetch 中断超时毫秒数;依赖 fetch 实现响应 signal。\n * @returns 内部管理器;不要绕过它直接更新已绑定的 storage。\n *\n * @example 模块内部上下文\n * ```ts\n * const storage = createMemoryTokenStorage()\n * const fetchTokens: typeof fetch = async () => Response.json({ data: { accessToken: 'test-access-next', refreshToken: 'test-refresh-next', expiresIn: 3600 } })\n * const manager = createTokenManager(storage, 'https://api.example.com/api/v1/auth/refresh', fetchTokens)\n * await manager.setTokens({ accessToken: 'test-access', refreshToken: 'test-refresh', expiresIn: 3600, tokenType: 'Bearer' })\n * const off = manager.onTokenRefreshed(tokens => { const lifetime = tokens.expiresIn })\n * try { const refreshed = await manager.refresh() }\n * finally { off(); await manager.clear() }\n * ```\n */\nexport function createTokenManager(\n storage: TokenStorage,\n refreshEndpointUrl: string,\n fetchFn: typeof globalThis.fetch,\n onRefreshFailed?: () => void,\n refreshTimeoutMs: number = 10_000,\n) {\n const callbacks: RefreshCallback[] = []\n let refreshPromise: Promise<TokenPair | null> | null = null\n let refreshController: AbortController | null = null\n let generation = 0\n let storageMutation: Promise<void> = Promise.resolve()\n\n /**\n * 按调用顺序提交异步存储写入;单次失败只拒绝该调用,后续清理/登录仍可运行。\n * @param operation - 本次需要排序的写入操作。\n * @returns 本次操作结果,不吞掉当前写入故障。\n *\n * @example 模块内部上下文\n * ```ts\n * const order: string[] = []\n * await mutateStorage(async () => { order.push('login') })\n * await mutateStorage(async () => { order.push('logout') })\n * ```\n */\n function mutateStorage<T>(operation: () => Promise<T>): Promise<T> {\n const pending = storageMutation.then(operation)\n storageMutation = pending.then(() => {}, () => {})\n return pending\n }\n\n /**\n * 同步推进身份代际并移除旧刷新去重状态;返回旧控制器供调用方排好新写入后中断。\n * @returns 旧刷新控制器或 null;即使底层忽略 abort,代际检查仍拒绝旧结果提交。\n *\n * @example 模块内部上下文\n * ```ts\n * const old = invalidateRefresh()\n * // 生产调用先排入新身份存储变更,再触发可能重入的 abort 监听器。\n * old?.abort()\n * ```\n */\n function invalidateRefresh(): AbortController | null {\n const previousController = refreshController\n generation++\n refreshController = null\n refreshPromise = null\n return previousController\n }\n\n /**\n * 仅当前刷新代际可尝试清理存储并通知失效;旧账号迟到 401 不影响新身份。\n * @param refreshGeneration - 发起刷新时记录的身份代际。\n * @returns 清理完成;存储失败拒绝,由 doRefresh 的异常分支处理。\n *\n * @example 模块内部上下文\n * ```ts\n * await failRefresh(generation)\n * // 传入旧 generation 会在串行写入队列中再次校验并跳过。\n * ```\n */\n async function failRefresh(refreshGeneration: number): Promise<void> {\n await mutateStorage(async () => {\n if (refreshGeneration !== generation)\n return\n await storage.clear()\n if (refreshGeneration === generation)\n onRefreshFailed?.()\n })\n }\n\n /**\n * 同一代际只发起一个刷新请求;旧请求 finally 不能清除新身份的在途状态。\n * @returns 成功且仍有效的 Token,或 null;读取适配器失败可能拒绝。\n *\n * @example 模块内部上下文\n * ```ts\n * const [first, second] = await Promise.all([refresh(), refresh()])\n * // 两个调用共享在途 fetch;结果仍以各自读取到的当前代际为准。\n * ```\n */\n async function refresh(): Promise<TokenPair | null> {\n // 并发去重:同一时刻只发一次 refresh,其它调用复用同一个 Promise\n if (refreshPromise)\n return refreshPromise\n const controller = new AbortController()\n const pending = doRefresh(generation, controller)\n refreshController = controller\n refreshPromise = pending\n try {\n return await pending\n }\n finally {\n // 旧请求结束不能移除换账号后新一轮刷新的去重状态。\n if (refreshPromise === pending) {\n refreshPromise = null\n refreshController = null\n }\n }\n }\n\n /**\n * 等待身份写入完成后读取刷新凭据,执行超时请求并在每个异步边界校验代际。\n * 4xx/无效数据尝试清理;5xx/网络故障保留凭据。只有当前代际成功写入后才通知订阅者。\n * @param refreshGeneration - 请求开始时的代际。\n * @param controller - 本次请求中断控制器;底层 fetch 应响应 signal。\n * @returns 当前刷新 Token 或 null;清理/回调等请求段异常记录为失败,不声称持久清理成功。\n *\n * @example 模块内部上下文\n * ```ts\n * const controller = new AbortController()\n * const tokens = await doRefresh(generation, controller)\n * if (tokens) { const lifetime = tokens.expiresIn }\n * ```\n */\n async function doRefresh(refreshGeneration: number, controller: AbortController): Promise<TokenPair | null> {\n // 登录/退出可能正在异步落盘,刷新只能读取其完成后的凭据。\n await storageMutation\n if (refreshGeneration !== generation)\n return null\n const refreshToken = await storage.getRefreshToken()\n if (refreshGeneration !== generation)\n return null\n if (!refreshToken) {\n logger.warn('Token refresh skipped, no refresh token available')\n onRefreshFailed?.()\n return null\n }\n\n logger.debug('Refreshing token')\n // 超时保护:避免 refresh 接口挂死时 dedup Promise 永不 resolve,连带所有等待者 hang。\n const timeoutId = setTimeout(() => controller.abort(), refreshTimeoutMs)\n try {\n // httpOnly cookie 模式:浏览器自动发送 Cookie,请求体无需携带 refreshToken;\n // 同时设置 credentials:'include' 以支持跨域 Cookie 发送。\n const useHttpOnlyCookie = refreshToken === HTTPONLY_COOKIE_SENTINEL\n const requestInit: RequestInit = {\n method: 'POST',\n body: useHttpOnlyCookie ? undefined : JSON.stringify({ refreshToken }),\n credentials: useHttpOnlyCookie ? 'include' : 'same-origin',\n signal: controller.signal,\n }\n if (!useHttpOnlyCookie) {\n requestInit.headers = { 'Content-Type': 'application/json' }\n }\n const response = await fetchFn(refreshEndpointUrl, requestInit)\n if (refreshGeneration !== generation)\n return null\n\n if (!response.ok) {\n // 仅在服务端明确返回 401/4xx 时才清除 Token(refresh token 已失效);\n // 5xx 作为服务端临时故障,保留 Token 让下一次请求重试。\n if (response.status >= 400 && response.status < 500) {\n await failRefresh(refreshGeneration)\n }\n else {\n logger.warn('Token refresh transient failure, keeping tokens', { status: response.status })\n }\n return null\n }\n\n const body = await response.json() as { data?: unknown }\n if (refreshGeneration !== generation)\n return null\n const parsed = RefreshTokenPayloadSchema.safeParse(readTokenPayload(body.data))\n if (!parsed.success) {\n logger.warn('Token refresh returned invalid data', { issues: parsed.error.issues })\n await failRefresh(refreshGeneration)\n return null\n }\n if (!useHttpOnlyCookie && !parsed.data.refreshToken) {\n logger.warn('Token refresh returned invalid data', { issues: [{ path: ['refreshToken'], message: 'Required' }] })\n await failRefresh(refreshGeneration)\n return null\n }\n\n // 默认值兜底,避免不安全强转\n const refreshTokenForStorage = parsed.data.refreshToken ?? HTTPONLY_COOKIE_SENTINEL\n const tokens: TokenPair = {\n accessToken: parsed.data.accessToken,\n refreshToken: refreshTokenForStorage,\n expiresIn: parsed.data.expiresIn ?? 3600,\n tokenType: 'Bearer',\n }\n const committed = await mutateStorage(async () => {\n if (refreshGeneration !== generation)\n return false\n await storage.setAccessToken(tokens.accessToken)\n if (refreshGeneration !== generation)\n return false\n await storage.setRefreshToken(refreshTokenForStorage)\n return refreshGeneration === generation\n })\n if (!committed || refreshGeneration !== generation)\n return null\n logger.info('Token refreshed successfully')\n for (const cb of callbacks) {\n if (refreshGeneration !== generation)\n return null\n cb(tokens)\n }\n return refreshGeneration === generation ? tokens : null\n }\n catch (error) {\n if (refreshGeneration !== generation)\n return null\n // 网络或 fetch 本身抛异常(超时、断网、CORS)视为可重试的暂时性错误,\n // 不清除 Token,仅记录日志。调用方可在下次请求时重试。\n logger.error('Token refresh failed (transient)', { error })\n return null\n }\n finally {\n clearTimeout(timeoutId)\n }\n }\n\n return {\n storage,\n refresh,\n async setTokens(tokens: TokenPair) {\n const previousController = invalidateRefresh()\n const mutationGeneration = generation\n const pending = mutateStorage(async () => {\n if (mutationGeneration !== generation)\n return\n // 替换完整凭据:新账号未提供 refresh token 时不能继续使用旧账号的值。\n await storage.clear()\n if (mutationGeneration !== generation)\n return\n await storage.setAccessToken(tokens.accessToken)\n if (tokens.refreshToken && mutationGeneration === generation)\n await storage.setRefreshToken(tokens.refreshToken)\n })\n // 先排好新身份写入,再触发 abort 事件;可重入的监听器只能读取新操作后的存储。\n previousController?.abort()\n await pending\n },\n async clear() {\n const previousController = invalidateRefresh()\n const pending = mutateStorage(() => storage.clear())\n previousController?.abort()\n await pending\n },\n onTokenRefreshed(callback: RefreshCallback): () => void {\n callbacks.push(callback)\n return () => {\n const idx = callbacks.indexOf(callback)\n if (idx >= 0)\n callbacks.splice(idx, 1)\n }\n },\n }\n}\n\n/**\n * Token 管理器内部契约,从真实工厂返回值推导;不属于包公开类型。\n *\n * @example 模块内部上下文\n * ```ts\n * const manager: TokenManager = createTokenManager(createMemoryTokenStorage(), 'https://api.example.com/api/v1/auth/refresh', fetch)\n * await manager.clear()\n * // clear 先废弃旧代际,再等待已有写入及本次清理完成。\n * ```\n */\nexport type TokenManager = ReturnType<typeof createTokenManager>\n\n/**\n * 从直接 tokens 或带 tokens 字段的 data 提取待校验内容;本函数不证明字段合法。\n * @param data - 已解析 JSON 包络的 data。\n * @returns 交给 RefreshTokenPayloadSchema 校验的未知值。\n *\n * @example 模块内部上下文\n * ```ts\n * const candidate = readTokenPayload({ tokens: { accessToken: 'test-access', expiresIn: 3600 } })\n * const parsed = RefreshTokenPayloadSchema.safeParse(candidate)\n * if (parsed.success) { const accessToken = parsed.data.accessToken }\n * ```\n */\nfunction readTokenPayload(data: unknown): unknown {\n if (typeof data === 'object' && data !== null && 'tokens' in data) {\n return (data as { tokens: unknown }).tokens\n }\n return data\n}\n","{\n \"apiClient_networkError\": \"Network request failed\",\n \"apiClient_timeout\": \"Request timeout\",\n \"apiClient_serverError\": \"Server error ({status})\",\n \"apiClient_unauthorized\": \"Unauthorized, please log in again\",\n \"apiClient_forbidden\": \"Access forbidden\",\n \"apiClient_notFound\": \"Resource not found\",\n \"apiClient_validationFailed\": \"Request validation failed\",\n \"apiClient_tokenRefreshFailed\": \"Token refresh failed, please log in again\",\n \"apiClient_notInitialized\": \"API client not initialized, please call apiClient.init() first\",\n \"apiClient_alreadyInitialized\": \"API client is already initialized; call apiClient.close() before init()\",\n \"apiClient_configError\": \"API client config error: {error}\",\n \"apiClient_responseValidationFailed\": \"Response validation failed\",\n \"apiClient_unknown\": \"Unknown error\"\n}\n","{\n \"apiClient_networkError\": \"ネットワークリクエストに失敗しました\",\n \"apiClient_timeout\": \"リクエストがタイムアウトしました\",\n \"apiClient_serverError\": \"サーバーエラー({status})\",\n \"apiClient_unauthorized\": \"認証されていません。再度ログインしてください\",\n \"apiClient_forbidden\": \"アクセスが拒否されました\",\n \"apiClient_notFound\": \"リソースが見つかりません\",\n \"apiClient_validationFailed\": \"リクエストの検証に失敗しました\",\n \"apiClient_tokenRefreshFailed\": \"トークンの更新に失敗しました。再度ログインしてください\",\n \"apiClient_notInitialized\": \"APIクライアントが初期化されていません。先にapiClient.init()を呼び出してください\",\n \"apiClient_alreadyInitialized\": \"APIクライアントは初期化済みです。init()の前にapiClient.close()を呼び出してください\",\n \"apiClient_configError\": \"APIクライアントの設定エラー: {error}\",\n \"apiClient_responseValidationFailed\": \"レスポンスの検証に失敗しました\",\n \"apiClient_unknown\": \"不明なエラー\"\n}\n","{\n \"apiClient_networkError\": \"网络请求失败\",\n \"apiClient_timeout\": \"请求超时\",\n \"apiClient_serverError\": \"服务器错误({status})\",\n \"apiClient_unauthorized\": \"未授权,请重新登录\",\n \"apiClient_forbidden\": \"无权限访问\",\n \"apiClient_notFound\": \"请求的资源不存在\",\n \"apiClient_validationFailed\": \"请求参数校验失败\",\n \"apiClient_tokenRefreshFailed\": \"Token刷新失败,请重新登录\",\n \"apiClient_notInitialized\": \"API 客户端未初始化,请先调用 apiClient.init()\",\n \"apiClient_alreadyInitialized\": \"API 客户端已初始化,请先调用 apiClient.close() 再重新初始化\",\n \"apiClient_configError\": \"API 客户端配置错误:{error}\",\n \"apiClient_responseValidationFailed\": \"响应数据校验失败\",\n \"apiClient_unknown\": \"未知错误\"\n}\n","/**\n * @h-ai/api-client — i18n\n *\n * 模块 i18n 文案访问入口。\n * @module api-client-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/**\n * 模块内合法翻译键;由中文词典推导并与现有英文词典配套维护。\n *\n * @example 模块内部上下文\n * ```ts\n * const key: ApiClientMessageKey = 'apiClient_notInitialized'\n * const message = apiClientM(key)\n * ```\n */\ntype ApiClientMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取 API Client 本地化消息;跟随 Core 当前 locale,不记录凭据或响应正文。\n * @param key - 真实模块消息键。\n * @param options - 语言覆盖及 params 插值选项。\n * @returns 已插值的中文或英文消息。\n *\n * @example 模块内部上下文\n * ```ts\n * const message = apiClientM('apiClient_configError', { locale: 'en-US', params: { error: 'Invalid endpoint' } })\n * ```\n */\nexport const apiClientM = core.i18n.createMessageGetter<ApiClientMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n","/** 认证表单动作:统一选择登录方式及安全 Token 存储。 */\nimport type { apiContract, IamLoginInput, IamRegisterInput } from '@h-ai/api-contract'\nimport type { HaiResult } from '@h-ai/core'\nimport type { ApiClient } from './api-client-types.js'\n\n/**\n * 认证表单登录输入;默认 password,ldap 使用 identifier/password,email/sms 使用 identifier/code。\n * method/code 为客户端表单辅助字段,按所选方式映射到标准 IAM procedure。\n *\n * @example\n * ```ts\n * import type { IamAuthLoginInput } from '@h-ai/api-client'\n * const input: IamAuthLoginInput = { identifier: 'alice@example.com', password: '', method: 'email', code: '123456' }\n * ```\n */\nexport type IamAuthLoginInput = IamLoginInput & { method?: 'password' | 'ldap' | 'email' | 'sms', code?: string }\n\n/**\n * 将含标准 IAM 契约的客户端绑定为认证表单动作;包用户通过 apiClient.createAuthActions 调用。\n * 登录/注册成功后等待 client.auth.setTokens,失败 HaiResult 原样返回;存储失败拒绝 Promise,不能据服务端成功就显示已登录。\n * 调用方必须先 init 并启用 auth;未启用 auth 时客户端 setTokens 不会建立 Token 管理器。\n * @param client - 具有标准 IAM procedure 与 setTokens 能力的已初始化客户端。\n * @param client.iam - 标准 IAM 契约的领域分组。\n * @param client.iam.auth - 公开配置、登录、注册、验证码和密码重置 procedure。\n * @param client.auth - 认证成功后的凭据写入能力(setTokens)。\n * @returns publicConfig、按方式登录、注册、验证码和密码重置动作。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const actions = apiClient.createAuthActions(apiClient)\n * // 运行时凭据来自已验证的表单;此处只请求公开配置,不提交示例密码。\n * const config = await actions.publicConfig()\n * if (!config.success) throw new Error(config.error.message)\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport function createIamAuthActions(client: {\n iam: { auth: Pick<ApiClient<{ iam: typeof apiContract.iam }>['iam']['auth'], 'publicConfig' | 'login' | 'loginWithLdap' | 'loginWithOtp' | 'register' | 'sendOtp' | 'requestPasswordReset' | 'confirmPasswordReset'> }\n auth: Pick<ApiClient<{ iam: typeof apiContract.iam }>['auth'], 'setTokens'>\n}) {\n /**\n * 认证成功后等待凭据保存;失败 HaiResult 不写存储,写入失败向调用方拒绝。\n * @param result - 标准 IAM 登录/注册结果,成功数据含 TokenPair。\n * @returns 同一结果;不能把服务端成功与本地安全存储成功混为一谈。\n *\n * @example 模块内部上下文\n * ```ts\n * const failure = { success: false as const, error: { code: 'test:denied', message: 'Denied', httpStatus: 401 } }\n * const result = await save(failure)\n * if (!result.success) { const message = result.error.message }\n * ```\n */\n async function save<T extends { tokens: Parameters<typeof client.auth.setTokens>[0] }>(result: HaiResult<T>) {\n if (result.success)\n await client.auth.setTokens(result.data.tokens)\n return result\n }\n return {\n publicConfig: () => client.iam.auth.publicConfig(),\n login: async (input: IamAuthLoginInput) => {\n const result = input.method === 'ldap'\n ? await client.iam.auth.loginWithLdap({ username: input.identifier, password: input.password })\n : input.method === 'email' || input.method === 'sms'\n ? await client.iam.auth.loginWithOtp({ identifier: input.identifier, code: input.code ?? '' })\n : await client.iam.auth.login({ identifier: input.identifier, password: input.password })\n return save(result)\n },\n register: async (input: IamRegisterInput) => save(await client.iam.auth.register(input)),\n sendOtp: (identifier: string) => client.iam.auth.sendOtp({ identifier }),\n requestPasswordReset: (identifier: string) => client.iam.auth.requestPasswordReset({ identifier }),\n confirmPasswordReset: (token: string, newPassword: string) => client.iam.auth.confirmPasswordReset({ token, newPassword }),\n }\n}\n","/**\n * @h-ai/api-client — 类型与错误码\n *\n * 公共类型(TokenPair、TokenStorage、ApiClientConfig 等)与标准错误集合(HaiApiClientError)。\n * 不包含实现,仅用于跨文件共享与对外导出。\n * @module api-client-types\n */\n\nimport type { ErrorInfo, HaiResult } from '@h-ai/core'\nimport type { CryptoFunctions } from '@h-ai/crypto'\nimport type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'\nimport type { JsonifiedClient } from '@orpc/openapi-client'\nimport { core } from '@h-ai/core'\n\n// ─── 错误码 ───────────────────────────────────────────────────────────────────\n\nconst ApiClientErrorInfo = {\n NETWORK_ERROR: '001:502',\n TIMEOUT: '002:504',\n SERVER_ERROR: '003:502',\n UNAUTHORIZED: '004:401',\n FORBIDDEN: '005:403',\n NOT_FOUND: '006:404',\n VALIDATION_FAILED: '007:400',\n TOKEN_REFRESH_FAILED: '008:401',\n PAYLOAD_TOO_LARGE: '020:413',\n NOT_INITIALIZED: '010:500',\n CONFIG_ERROR: '011:500',\n UNKNOWN: '099:500',\n} as const satisfies ErrorInfo\n\n/**\n * 客户端标准错误集合;包含稳定 code/httpStatus 与构造错误的方法,业务调用先检查 success。\n * 传输失败在 procedure 代理中映射;服务端 HaiResult 的业务错误保持原契约。\n *\n * @example\n * ```ts\n * import { apiClient, HaiApiClientError } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.currentUser()\n * if (!result.success && result.error.code === HaiApiClientError.UNAUTHORIZED.code) {\n * // 由应用切换到登录页面;不输出 Token。\n * const message = result.error.message\n * }\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport const HaiApiClientError = core.error.buildHaiErrorsDef('api-client', ApiClientErrorInfo)\n\n// ─── Token ────────────────────────────────────────────────────────────────────\n\n/**\n * 短期 access token、可选 refresh token 与有效期;HTTP 头使用 Bearer。\n * Cookie 模式的 refresh token 由服务端管理,不能要求浏览器读取或持久化它。\n *\n * @example\n * ```ts\n * import type { TokenPair } from '@h-ai/api-client'\n * const tokens: TokenPair = { accessToken: 'test-access', expiresIn: 3600, tokenType: 'Bearer' }\n * // 示例为测试凭据;生产 Token 只能来自可信服务端认证响应。\n * ```\n */\nexport interface TokenPair {\n /** 短期访问凭证,请求时作为 `Authorization: Bearer ...`。 */\n readonly accessToken: string\n /** 长期刷新凭证;httpOnly cookie 模式下由服务端管理,响应体中可能不存在。 */\n readonly refreshToken?: string\n /** accessToken 有效期秒数。 */\n readonly expiresIn: number\n /** 固定为 `'Bearer'`。 */\n readonly tokenType: 'Bearer'\n}\n\n/**\n * 异步安全凭据存储契约;null 仅表示无可用凭据,读写故障须拒绝 Promise。\n * 浏览器使用内存 access token/httpOnly refresh Cookie,原生端注入平台安全存储,禁止敏感 Token 放入 localStorage。\n * 已绑定客户端的身份切换通过 client.auth,避免绕过刷新代际与串行写入;部分写入失败须执行清理并报告清理失败。\n *\n * 内置实现:\n * - `apiClient.tokenStorage.httpOnlyCookie()` — **默认**;浏览器端推荐,refresh token 由服务端管理,防 XSS。\n * - `apiClient.tokenStorage.memory()` — Node.js 测试 / SSR(需显式传入)。\n *\n * Capacitor / 小程序场景请由对应模块提供(例如 `@h-ai/capacitor`)。\n *\n * @example\n * ```ts\n * import { apiClient, type TokenStorage } from '@h-ai/api-client'\n * const storage: TokenStorage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * const accessToken = await storage.getAccessToken()\n * await storage.clear()\n * ```\n */\nexport interface TokenStorage {\n /**\n * 读取本适配器可用 access token;无凭据返回 null,存储故障拒绝。\n * @returns Token 或 null;不能把读取失败当成不存在。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * const accessToken = await storage.getAccessToken()\n * ```\n */\n readonly getAccessToken: () => Promise<string | null>\n /**\n * 保存 access token;已绑定客户端应通过 auth.setTokens 维护代际。\n * @param token - 可信服务端认证返回的 access token。\n * @returns 写入完成;底层故障须拒绝。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setAccessToken('test-access')\n * await storage.clear()\n * ```\n */\n readonly setAccessToken: (token: string) => Promise<void>\n /**\n * 读取 refresh token 或 Cookie 模式内部哨兵;null 仅表示没有凭据。\n * @returns Token、模式哨兵或 null,不是对 httpOnly Cookie 内容的脚本读取。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * const refreshToken = await storage.getRefreshToken()\n * // 内存适配器初始为空;Cookie 适配器由管理器识别模式哨兵。\n * ```\n */\n readonly getRefreshToken: () => Promise<string | null>\n /**\n * 保存 refresh token;Cookie 实现不通过脚本修改服务端 Cookie。\n * @param token - 服务端返回的刷新凭据;禁止写入 Web Storage。\n * @returns 写入完成;原生安全存储故障须拒绝。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.setRefreshToken('test-refresh')\n * await storage.clear()\n * ```\n */\n readonly setRefreshToken: (token: string) => Promise<void>\n /**\n * 清空适配器可管理的凭据;Cookie 实现只能清内存 access token。\n * @returns 清理完成;失败须拒绝,服务端 Cookie/会话另由退出接口撤销。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const storage = apiClient.tokenStorage.memory()\n * await storage.clear()\n * const remaining = await storage.getAccessToken()\n * ```\n */\n readonly clear: () => Promise<void>\n}\n\n// ─── 配置 ─────────────────────────────────────────────────────────────────────\n\n/**\n * 启用 Token 注入与自动刷新;默认 Cookie 适配器,refreshPath 相对 baseUrl。\n * 网络/超时/5xx 为暂时失败,保留凭据;明确失效才清理并通知。回调不应抛出或记录 Token。\n *\n * @example\n * ```ts\n * import { apiClient, type AuthConfig } from '@h-ai/api-client'\n * const auth: AuthConfig = { storage: apiClient.tokenStorage.memory(), refreshPath: '/auth/refresh', refreshTimeoutMs: 5000, onTokenRefreshed: tokens => { const lifetime = tokens.expiresIn } }\n * // Node/SSR 选择独立内存存储;浏览器通常直接配置 auth: {}。\n * ```\n */\nexport interface AuthConfig {\n /**\n * Token 存储适配器;默认 `apiClient.tokenStorage.httpOnlyCookie()`(refresh token 由服务端管理,防 XSS)。\n *\n * - SSR / Node.js 测试场景请显式传入 `apiClient.tokenStorage.memory()`。\n * - 浏览器刷新后通过 httpOnly Cookie 恢复会话,不将 Token 持久化到 Web Storage。\n * - Capacitor / 小程序请使用对应平台的安全存储适配器(如 `@h-ai/capacitor`)。\n */\n readonly storage?: TokenStorage\n /** 刷新接口路径,相对于 `baseUrl`,默认 `/auth/refresh`。 */\n readonly refreshPath?: string\n /**\n * 刷新请求超时(ms),默认 10_000。\n * 避免 refresh 接口上游挂死时所有 dedup 调用方一起 hang。\n * 超时后按现有 transient 失败处理(不清 token,下次请求可重试)。\n */\n readonly refreshTimeoutMs?: number\n /** 刷新成功回调,用于同步状态(例如多 Tab 广播)。 */\n readonly onTokenRefreshed?: (tokens: TokenPair) => void\n /** 刷新失败回调(如 refresh token 已失效),通常用于跳转登录页。 */\n readonly onRefreshFailed?: () => void\n}\n\n/**\n * 客户端初始化配置;baseUrl 包含 API 前缀,headers/fetch 用于平台适配。\n * auth 缺省时不创建 TokenManager;timeout 是底层请求中断期限,不代表服务端已取消副作用。\n * transport 需要已初始化 crypto,首次业务请求前协商会话;客户端不接收服务端私钥。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientConfig } from '@h-ai/api-client'\n * const config: ApiClientConfig = { baseUrl: 'https://api.example.com/api/v1', auth: { storage: apiClient.tokenStorage.memory() }, timeout: 15000, clientName: 'orders-web', headers: () => ({ 'x-app-channel': 'web' }) }\n * const initialized = await apiClient.init(config)\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * ```\n */\nexport interface ApiClientConfig {\n /** API service 完整 baseUrl,通常含 `apiPrefix`,例如 `https://api.example.com/api/v1`。 */\n readonly baseUrl: string\n /** 认证配置;不传则不启用 Token 注入与自动刷新。 */\n readonly auth?: AuthConfig\n /** 请求超时(ms),默认 30000。 */\n readonly timeout?: number\n /** 客户端标识,写入 `x-request-client` 请求头,默认 `hai-api-client`。 */\n readonly clientName?: string\n /** 额外请求头,可为静态对象或异步函数。 */\n readonly headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)\n /** 自定义 fetch(测试 mock / Capacitor / 小程序桥接)。 */\n readonly fetch?: typeof globalThis.fetch\n /**\n * 启用端到端传输加密(opt-in)。\n *\n * 配置后所有出站请求会在底层 fetch 之上做一次混合加密,并自动解密\n * 标记为 `X-Encrypted: true` 的响应。服务端需启用对应的 `serv.createApp({ transport })`。\n *\n * `crypto` 字段结构与 `@h-ai/crypto` 的 `crypto` 实例兼容;通常直接传入:\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * import { crypto } from '@h-ai/crypto'\n *\n * const cryptography = await crypto.init()\n * if (!cryptography.success) throw new Error(cryptography.error.message)\n * try {\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', transport: { crypto } })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * }\n * finally { await crypto.close() }\n * ```\n */\n readonly transport?: ApiClientTransportConfig\n}\n\n// ─── 传输加密 ────────────────────────────────────────────────────────────────\n\n/**\n * 可选加密会话配置;密钥协商路径相对 baseUrl,并与服务端配置匹配。\n * crypto 必须先初始化;服务器版本公钥通过受保护的 TLS 握手取得,不配置服务端私钥。\n * 传输防重放不能代替业务幂等性,失败后不要自动重放非幂等操作。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientTransportConfig } from '@h-ai/api-client'\n * import { crypto } from '@h-ai/crypto'\n * const cryptography = await crypto.init()\n * if (!cryptography.success) throw new Error(cryptography.error.message)\n * try {\n * const transport: ApiClientTransportConfig = { crypto, keyExchangePath: '/_hai/key-exchange' }\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', transport })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * }\n * finally { await crypto.close() }\n * ```\n */\nexport interface ApiClientTransportConfig {\n /** 可选本地明文 UTF-8 字节上限;缺省自动跟随服务端,指定时取较小值。 */\n readonly maxPlaintextBytes?: number\n /**\n * crypto 服务实例(通常直接传入 `@h-ai/crypto` 的 `crypto`)。\n * api-client 通过 `crypto.transport.createClient(...)` 创建会话。\n */\n readonly crypto: CryptoFunctions\n /**\n * 密钥协商端点路径,相对于 `baseUrl`,默认 `/_hai/key-exchange`。\n *\n * 必须与服务端 `serv.createApp({ transport: { keyExchangePath } })` 一致。\n */\n readonly keyExchangePath?: string\n}\n\n// ─── Client 接口 ──────────────────────────────────────────────────────────────\n\n/**\n * 当前客户端的凭据辅助操作;仅 init 启用 auth 时建立实际管理器。\n * setTokens/clear 撤销旧刷新代际并等待串行存储变更;故障拒绝 Promise,调用方处理失败。\n * clear 不能撤销服务端会话或删除 httpOnly Cookie,退出仍需服务端接口成功。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientAuth } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const auth: ApiClientAuth = apiClient.auth\n * const off = auth.onTokenRefreshed(tokens => { const lifetime = tokens.expiresIn })\n * off()\n * await auth.clear()\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport interface ApiClientAuth {\n /**\n * 替换整对凭据并同步废弃旧刷新代际;缺省 refreshToken 时清除旧值。\n * @param tokens - 服务端成功认证返回的真实 TokenPair。\n * @returns 写入完成;安全存储失败拒绝 Promise,调用方应清理部分凭据并报告清理失败。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const form = { identifier: 'alice', password: 'test-password' }\n * const login = await apiClient.iam.auth.login(form)\n * if (!login.success) throw new Error(login.error.message)\n * try { await apiClient.auth.setTokens(login.data.tokens) }\n * catch (writeError) {\n * try { await apiClient.auth.clear() }\n * catch (cleanupError) { throw new AggregateError([writeError, cleanupError], 'Credential persistence and cleanup failed') }\n * throw writeError\n * }\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly setTokens: (tokens: TokenPair) => Promise<void>\n /**\n * 同步废弃旧刷新代际并等待存储清理;失败拒绝 Promise,不声称已持久删除。\n * 服务端会话与 httpOnly Cookie 仍需服务端退出撤销;本操作不能替代退出接口。\n * @returns 本地清理完成,不返回 HaiResult。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const logout = await apiClient.iam.auth.logout({})\n * await apiClient.auth.clear()\n * if (!logout.success) throw new Error(logout.error.message)\n * // 只有服务端退出和本地清理都成功,才能确认两侧均已处理。\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly clear: () => Promise<void>\n /**\n * 订阅当前管理器刷新成功事件;回调在写入后同步调用,避免抛错或输出 Token。\n * @param callback - 接收 TokenPair 的同步回调。\n * @returns 取消订阅函数;未启用 auth 时返回空操作。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const off = apiClient.auth.onTokenRefreshed(tokens => { const expiresIn = tokens.expiresIn })\n * off()\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly onTokenRefreshed: (callback: (tokens: TokenPair) => void) => () => void\n}\n\n/**\n * 客户端生命周期与认证辅助能力;init 的 HaiResult 必须先检查 success。\n * 同一实例并发 init 共享在途初始化,已初始化后再 init 返回 CONFIG_ERROR;切换配置先 close。\n * config 为脱敏快照;close 清理运行状态/加密会话,不等于服务端退出或凭据存储清理。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClientLifecycle } from '@h-ai/api-client'\n * const client: ApiClientLifecycle = apiClient\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1' })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * const active = client.isInitialized\n * const snapshot = client.config\n * await client.close()\n * ```\n */\nexport interface ApiClientLifecycle {\n /**\n * 观察一次逻辑调用的最终结果;刷新重试保留关联 ID,不包含请求正文或凭据。\n * close 清理订阅;回调异常不影响请求,返回函数可提前取消。\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const unsubscribe = apiClient.observeRequests(record => { const requestId = record.requestId })\n * unsubscribe()\n * ```\n */\n readonly observeRequests: (observer: ApiRequestObserver) => () => void\n /**\n * 构造并提交客户端运行状态;初始化失败返回 CONFIG_ERROR,未提交半初始化对象。\n * 并发 init 共享同一初始化;已初始化再调用会失败,切换配置先 close。\n * @param config - baseUrl、认证、fetch 与可选加密配置。\n * @returns HaiResult;必须先判断 success,再发起 procedure。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.publicConfig()\n * if (!result.success) throw new Error(result.error.message)\n * }\n * finally { await apiClient.close() }\n * ```\n */\n readonly init: (config: ApiClientConfig) => Promise<HaiResult<void>>\n /**\n * 销毁当前加密会话并清除运行状态,允许重新 init;不发送服务端退出、不清空外置存储。\n * @returns 资源清理完成;不是 HaiResult,不应读取 success。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * await apiClient.close()\n * const reinitialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!reinitialized.success) throw new Error(reinitialized.error.message)\n * await apiClient.close()\n * ```\n */\n readonly close: () => Promise<void>\n /** 当前脱敏后的运行配置快照;未初始化时为 `null`。 */\n readonly config: ApiClientConfig | null\n /** 是否已完成初始化。 */\n readonly isInitialized: boolean\n /** Token 辅助操作。 */\n readonly auth: ApiClientAuth\n}\n\n/**\n * 逻辑请求诊断摘要。HTTP 200 的失败 HaiResult 同样 success=false。\n * @example\n * ```ts\n * import type { ApiRequestRecord } from '@h-ai/api-client'\n * const record: ApiRequestRecord = { requestId: 'request-1', procedure: 'orders.get', timestamp: 1, duration: 10, success: false, attempts: 1, errorCode: 'orders:missing' }\n * ```\n */\nexport interface ApiRequestRecord {\n readonly requestId: string\n readonly procedure: string\n readonly timestamp: number\n readonly duration: number\n readonly success: boolean\n readonly attempts: number\n readonly errorCode?: string\n readonly method?: string\n readonly url?: string\n readonly status?: number\n}\n\n/**\n * 同步观察者,只做有界收集。\n * @example\n * ```ts\n * import { apiClient, type ApiRequestObserver } from '@h-ai/api-client'\n * const observer: ApiRequestObserver = record => { const failed = !record.success }\n * const unsubscribe = apiClient.observeRequests(observer)\n * unsubscribe()\n * ```\n */\nexport type ApiRequestObserver = (record: ApiRequestRecord) => void\n\n/**\n * 由 oRPC 契约推导的 JSON typed client,与生命周期组合;procedure 的输入/输出由契约决定。\n * 调用先检查初始化和业务 HaiResult;自定义契约不能覆盖 init/close/config/isInitialized/auth 顶层成员。\n *\n * @example\n * ```ts\n * import { apiClient, type ApiClient } from '@h-ai/api-client'\n * import { apiContract } from '@h-ai/api-contract'\n * const contract = apiContract.create({ iam: apiContract.iam })\n * const client: ApiClient<typeof contract> = apiClient.create(contract)\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await client.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * const userId = result.data.id\n * }\n * finally { await client.close() }\n * ```\n */\nexport type ApiClient<TContract extends AnyContractRouter>\n = ApiClientLifecycle & JsonifiedClient<ContractRouterClient<TContract>>\n","/**\n * @h-ai/api-client — 主实现\n *\n * 包含两部分:\n * - `createApiClient(contract)`:根据 oRPC contract 创建 typed client 工厂(内部实现)。\n * - `apiClient`:统一公开入口;自身就是默认单例,同时挂载 `create` / `tokenStorage`。\n *\n * Client 同时具备生命周期能力(init/close/config/isInitialized/auth)和 contract\n * 推导出的 procedure 嵌套调用能力(如 `client.iam.auth.login(input)`)。\n *\n * 内置能力:\n * - 401 自动 refresh + 单次重试\n * - 并发 refresh 共享 Promise\n * - 请求超时(AbortController)\n * - `ORPCError` → `HaiApiClientError` 标准化映射\n * - 未初始化时调用 procedure 返回 `NOT_INITIALIZED`,不抛异常\n *\n * @example 默认单例\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const me = await apiClient.iam.auth.currentUser()\n * if (!me.success) throw new Error(me.error.message)\n * const userId = me.data.id\n * }\n * finally { await apiClient.close() }\n * ```\n *\n * @example 自定义 contract\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * import { apiContract } from '@h-ai/api-contract'\n *\n * const client = apiClient.create(apiContract.create({ iam: apiContract.iam }))\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await client.iam.auth.publicConfig()\n * if (!result.success) throw new Error(result.error.message)\n * }\n * finally { await client.close() }\n * ```\n *\n * @module api-client-main\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { TransportClient } from '@h-ai/crypto'\nimport type { AnyContractRouter, ContractRouterClient } from '@orpc/contract'\nimport type { JsonifiedClient } from '@orpc/openapi-client'\nimport type { TokenManager } from './api-client-auth.js'\nimport type {\n ApiClient,\n ApiClientAuth,\n ApiClientConfig,\n ApiClientLifecycle,\n ApiRequestObserver,\n ApiRequestRecord,\n} from './api-client-types.js'\nimport { apiContract } from '@h-ai/api-contract'\nimport { core, err, ok } from '@h-ai/core'\nimport { TransportBodyTooLargeError, TransportTimeError } from '@h-ai/crypto'\nimport { createORPCClient, ORPCError } from '@orpc/client'\nimport { OpenAPILink } from '@orpc/openapi-client/fetch'\nimport {\n createHttpOnlyCookieTokenStorage,\n createMemoryTokenStorage,\n createTokenManager,\n} from './api-client-auth.js'\nimport { apiClientM } from './api-client-i18n.js'\nimport { createIamAuthActions } from './api-client-iam.js'\nimport { HaiApiClientError } from './api-client-types.js'\n\nconst DEFAULT_TIMEOUT = 30_000\nconst DEFAULT_CLIENT_NAME = 'hai-api-client'\nconst DEFAULT_REFRESH_PATH = apiContract.pathOf(apiContract.iam.auth.refresh)\nconst AUTH_ENTRY_PATHS = [\n apiContract.pathOf(apiContract.iam.auth.login),\n apiContract.pathOf(apiContract.iam.auth.loginWithOtp),\n apiContract.pathOf(apiContract.iam.auth.loginWithLdap),\n apiContract.pathOf(apiContract.iam.auth.requestPasswordReset),\n apiContract.pathOf(apiContract.iam.auth.confirmPasswordReset),\n apiContract.pathOf(apiContract.iam.auth.register),\n]\nconst TRAILING_SLASHES_REGEX = /\\/+$/\nconst REQUEST_OBSERVATION = Symbol('hai.api-client.request-observation')\n/**\n * 内部构造的可变摘要,不对消费者暴露写入能力。\n * @example 模块内部上下文\n * ```ts\n * function update(record: MutableRequestRecord) { record.attempts += 1 }\n * ```\n */\ntype MutableRequestRecord = { -readonly [Key in keyof ApiRequestRecord]: ApiRequestRecord[Key] }\n\n/** Token 存储方案集合:内存 / httpOnly Cookie。 */\nconst tokenStorage = {\n /** 内存存储——SSR / Node 测试或一次性会话使用。 */\n memory: createMemoryTokenStorage,\n /** httpOnly Cookie 存储——浏览器场景推荐方案。 */\n httpOnlyCookie: createHttpOnlyCookieTokenStorage,\n} as const\n\n// ─── 工厂 ─────────────────────────────────────────────────────────────────────\n\n/**\n * 按真实 oRPC 契约创建独立 typed client;公开调用入口为 apiClient.create(contract)。\n * init 返回 HaiResult,成功后才调用 procedure;已初始化实例必须先 close 再换配置。\n * procedure 将传输异常转换为 HaiResult;auth.setTokens/clear 的安全存储失败仍会拒绝 Promise。\n * SSR 应为每个请求创建客户端和内存存储;不共享默认单例中的用户凭据。\n * @param contract - 已定义的嵌套 oRPC contract;顶层命名不能与生命周期成员冲突。\n * @returns 生命周期、认证辅助操作与契约推导出的 procedure 代理。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * import { apiContract } from '@h-ai/api-contract'\n * const client = apiClient.create(apiContract.create({ iam: apiContract.iam }))\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: { storage: apiClient.tokenStorage.memory() }, timeout: 10000 })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await client.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * const userId = result.data.id\n * }\n * finally { await client.close() }\n * ```\n */\nexport function createApiClient<const TContract extends AnyContractRouter>(\n contract: TContract,\n): ApiClient<TContract> {\n // 客户端运行时状态\n const state: {\n config: ApiClientConfig | null\n rawClient: JsonifiedClient<ContractRouterClient<TContract>> | null\n tokenManager: TokenManager | undefined\n transport: TransportClient | undefined\n observers: Set<ApiRequestObserver>\n } = {\n config: null,\n rawClient: null,\n tokenManager: undefined,\n transport: undefined,\n observers: new Set(),\n }\n\n const auth: ApiClientAuth = {\n async setTokens(tokens) { await state.tokenManager?.setTokens(tokens) },\n async clear() { await state.tokenManager?.clear() },\n onTokenRefreshed(callback) {\n return state.tokenManager?.onTokenRefreshed(callback) ?? (() => {})\n },\n }\n\n let initPromise: Promise<HaiResult<void>> | null = null\n const lifecycle: ApiClientLifecycle = {\n async init(config) {\n // 并发重入防护:同时多次 init() 只执行一次。\n // 另一个调用方 await 同一个 in-flight Promise,避免重复创建 raw client / token manager。\n if (initPromise)\n return initPromise\n // 已初始化后必须先 close() 再重新 init,避免旧 transport 会话被新状态覆盖后无法 destroy。\n if (state.rawClient) {\n return err(\n HaiApiClientError.CONFIG_ERROR,\n apiClientM('apiClient_alreadyInitialized'),\n )\n }\n initPromise = (async (): Promise<HaiResult<void>> => {\n let nextTransport: TransportClient | undefined\n try {\n nextTransport = buildTransportSession(config)\n const nextTokenManager = buildTokenManager(config, nextTransport)\n const nextRawClient = buildRawClient(contract, config, nextTokenManager, nextTransport)\n\n // 所有可能失败的构建步骤完成后再提交状态,避免调用方看到半初始化 client。\n state.config = config\n state.transport = nextTransport\n state.tokenManager = nextTokenManager\n state.rawClient = nextRawClient\n return ok(undefined)\n }\n catch (error) {\n // 构建过程中如果 transport 已创建但后续失败,必须主动释放,避免会话/定时器泄漏。\n nextTransport?.destroy()\n state.config = null\n state.rawClient = null\n state.tokenManager = undefined\n state.transport = undefined\n return err(\n HaiApiClientError.CONFIG_ERROR,\n apiClientM('apiClient_configError', { params: { error: String(error) } }),\n error,\n )\n }\n })()\n try {\n return await initPromise\n }\n finally {\n initPromise = null\n }\n },\n async close() {\n state.observers.clear()\n state.transport?.destroy()\n state.config = null\n state.rawClient = null\n state.tokenManager = undefined\n state.transport = undefined\n },\n get config() { return state.config ? core.sanitize.sanitizeSensitiveFields(state.config) : null },\n get isInitialized() { return state.rawClient !== null },\n auth,\n observeRequests(observer) {\n state.observers.add(observer)\n return () => {\n state.observers.delete(observer)\n }\n },\n }\n\n // 生命周期成员优先,其余属性只记录真实契约路径,末端调用才交给 rawClient。\n // 契约顶层不能与 init/close/config/isInitialized/auth 重名;此处不提供额外路由注册。\n return new Proxy({}, {\n get(_target, property) {\n // Proxy 的属性键不一定是 string,也可能是 Symbol(例如某些运行时内部探测)。\n // 本 client 只处理字符串路径;非字符串键直接忽略,避免把 Symbol 误当成 API 路径的一部分。\n if (typeof property !== 'string')\n return undefined\n\n // 顶层字段命中 lifecycle 时,直接返回真实成员。\n // 例如:\n // - property === 'init' → 返回 init 函数\n // - property === 'auth' → 返回 auth 对象\n // 这样 `client.init()` / `client.auth.clear()` 就能像普通对象一样工作。\n if (property in lifecycle) {\n return Reflect.get(lifecycle, property)\n }\n\n // 走到这里,说明它不是生命周期字段,那就把它当成 contract 路径的第一段。\n // 例如访问 `client.iam` 时,这里会创建一个 path = ['iam'] 的嵌套 Proxy。\n // 后续如果继续访问 `.auth.login`,path 会逐步变成:\n // ['iam', 'auth'] → ['iam', 'auth', 'login']\n // 最终在函数调用时再映射到真正的 rawClient.iam.auth.login(...args)。\n return createProcedureProxy(state, [property])\n },\n // 这里的返回值在运行时是一个 Proxy,但从“可用能力”上看,它符合 `ApiClient<TContract>`:\n // - 一部分成员来自 lifecycle\n // - 另一部分成员来自 contract 推导出的 procedure 层级\n // 因此使用类型断言把这个动态对象声明为最终对外的 typed client。\n }) as ApiClient<TContract>\n}\n\n// ─── 默认单例 ─────────────────────────────────────────────────────────────────\n\n/**\n * 默认 API typed client:绑定 `iam` / `storage` / `ai` 三个领域。\n *\n * 大多数应用只需 `import { apiClient }` 并调用 `apiClient.init()` 即可。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n *\n * const initialized = await apiClient.init({\n * baseUrl: 'http://localhost:3000/api/v1',\n * auth: { refreshPath: '/auth/refresh' },\n * })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * }\n * finally { await apiClient.close() }\n * ```\n */\nconst defaultApiContract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })\n\n/**\n * 默认 iam/storage/ai typed client 与 create、tokenStorage、createAuthActions 命名空间的交叉类型。\n * 具体项目契约使用 apiClient.create 的返回类型,不给默认单例附加未知 procedure。\n *\n * @example\n * ```ts\n * import { apiClient, type DefaultApiClient } from '@h-ai/api-client'\n * const client: DefaultApiClient = apiClient\n * const initialized = await client.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try { const actions = client.createAuthActions(client); const result = await actions.publicConfig(); if (!result.success) throw new Error(result.error.message) }\n * finally { await client.close() }\n * ```\n */\nexport type DefaultApiClient = ApiClient<typeof defaultApiContract> & {\n readonly create: typeof createApiClient\n readonly tokenStorage: typeof tokenStorage\n readonly createAuthActions: typeof createIamAuthActions\n}\n\n/**\n * API Client 公开入口:默认 iam/storage/ai 单例,以及独立实例、安全存储和认证动作工厂。\n * 未启用 auth 时不注入或刷新 Token;浏览器 auth: {} 使用 httpOnly Cookie 方案。\n * close 释放客户端运行状态/加密会话,不能替代服务端退出或 auth.clear。\n *\n * @example\n * ```ts\n * import { apiClient } from '@h-ai/api-client'\n * const initialized = await apiClient.init({ baseUrl: 'https://api.example.com/api/v1', auth: {} })\n * if (!initialized.success) throw new Error(initialized.error.message)\n * try {\n * const result = await apiClient.iam.auth.currentUser()\n * if (!result.success) throw new Error(result.error.message)\n * const permissions = result.data.permissions\n * }\n * finally { await apiClient.close() }\n * ```\n */\nexport const apiClient = new Proxy(createApiClient(defaultApiContract) as ApiClient<typeof defaultApiContract>, {\n get(target, property, receiver) {\n if (property === 'create')\n return createApiClient\n if (property === 'tokenStorage')\n return tokenStorage\n if (property === 'createAuthActions')\n return createIamAuthActions\n return Reflect.get(target as object, property, receiver)\n },\n}) as DefaultApiClient\n\n// ─── 内部:procedure 代理 ─────────────────────────────────────────────────────\n\n/**\n * 构造只记录属性路径的内部代理,末端调用交给 callProcedure;不在访问属性时发送请求。\n * @param state - 当前 rawClient,可在 close 后变为 null。\n * @param state.rawClient - 当前 oRPC 客户端;未初始化或关闭后为 null。\n * @param state.observers - 本实例逻辑请求订阅集合。\n * @param path - 已访问的 contract 属性路径。\n * @returns 动态代理;由契约公开类型约束调用路径,不对外承诺任意字符串 API。\n *\n * @example 模块内部上下文\n * ```ts\n * const proxy = createProcedureProxy({ rawClient: null }, ['iam'])\n * // 直到 apply 才调用 callProcedure;未初始化返回失败 HaiResult。\n * ```\n */\nfunction createProcedureProxy<TContract extends AnyContractRouter>(\n state: { rawClient: JsonifiedClient<ContractRouterClient<TContract>> | null, observers?: Set<ApiRequestObserver> },\n path: string[],\n): unknown {\n return new Proxy(() => undefined, {\n get(_t, property) {\n if (typeof property !== 'string')\n return undefined\n return createProcedureProxy(state, [...path, property])\n },\n apply(_t, _thisArg, args) {\n return callObservedProcedure(state.rawClient, path, args, state.observers)\n },\n })\n}\n\n/** 不复制业务入参;通过 oRPC 每次调用的 context 将同一关联传给传输层。 */\nasync function callObservedProcedure(rawClient: unknown, path: string[], args: unknown[], observers?: ReadonlySet<ApiRequestObserver>): Promise<unknown> {\n const record: MutableRequestRecord = { requestId: core.context.get().requestId ?? core.id.uuid(), procedure: path.join('.'), timestamp: Date.now(), duration: 0, success: false, attempts: 0 }\n const provided = args[1] && typeof args[1] === 'object' ? args[1] as Record<string, unknown> : {}\n const providedContext = provided.context && typeof provided.context === 'object' ? provided.context : {}\n const options = { ...provided, context: { ...providedContext, [REQUEST_OBSERVATION]: record } }\n let result = await callProcedure(rawClient, path, [args[0], options])\n record.duration = Math.max(0, Date.now() - record.timestamp)\n record.success = !(result && typeof result === 'object' && 'success' in result && result.success === false)\n if (result && typeof result === 'object' && 'error' in result && result.error && typeof result.error === 'object' && 'code' in result.error) {\n record.errorCode = String(result.error.code)\n if ('message' in result.error && typeof result.error.message === 'string') {\n const error = result.error as Parameters<typeof core.error.toPublic>[0]\n const receivedId = error.traceId && /^[\\w.:-]{1,128}$/.test(error.traceId) ? error.traceId : record.requestId\n record.requestId = receivedId\n const safe = core.error.toPublic({ ...error, publicExt: error.ext }, receivedId)\n if (error.cause && typeof error.cause === 'object' && !Array.isArray(error.cause)) {\n const metadata: Record<string, unknown> = {}\n for (const key of ['orpcCode', 'status', 'transportCode', 'direction', 'stage', 'limitBytes', 'actualBytes']) {\n const value: unknown = Reflect.get(error.cause, key)\n if (typeof value === 'number' || typeof value === 'string')\n metadata[key] = core.sanitize.sanitizeSensitiveValue(value, key)\n }\n if (Object.keys(metadata).length)\n safe.cause = metadata\n }\n core.logger.error('API request failed', { traceId: safe.traceId, requestId: record.requestId, procedure: record.procedure, status: record.status, error: result.error })\n result = { ...result, error: safe }\n }\n }\n for (const observer of observers ?? []) {\n try {\n const observed: unknown = observer(Object.freeze({ ...record }))\n if (observed instanceof Promise)\n void observed.catch(() => {})\n }\n catch { /* 观察失败不改变请求结果。 */ }\n }\n return result\n}\n\n/**\n * 解析真实 rawClient 中的 procedure 并调用,将调用异常映射为标准 HaiResult 失败。\n * @param rawClient - 已构建的 oRPC client;缺省时返回 NOT_INITIALIZED。\n * @param path - 契约层级路径。\n * @param args - 原始 procedure 参数。\n * @returns 原契约结果或客户端失败,不把失败改成成功。\n *\n * @example 模块内部上下文\n * ```ts\n * const result = await callProcedure(null, ['iam', 'auth', 'currentUser'], [])\n * // 实际得到 NOT_INITIALIZED;返回值为 unknown,外部类型来自真实契约。\n * ```\n */\nasync function callProcedure(\n rawClient: unknown,\n path: string[],\n args: unknown[],\n): Promise<unknown> {\n if (!rawClient) {\n return err(HaiApiClientError.NOT_INITIALIZED, apiClientM('apiClient_notInitialized'))\n }\n\n // 沿 path 解析 rawClient[a][b][c]...\n let target: unknown = rawClient\n for (const segment of path) {\n if ((typeof target !== 'object' || target === null) && typeof target !== 'function') {\n return err(HaiApiClientError.UNKNOWN, apiClientM('apiClient_unknown'))\n }\n target = Reflect.get(target as object, segment)\n }\n if (typeof target !== 'function') {\n return err(HaiApiClientError.UNKNOWN, apiClientM('apiClient_unknown'))\n }\n\n try {\n return await (target as (...a: unknown[]) => Promise<unknown>)(...args)\n }\n catch (error) {\n return mapClientError(error)\n }\n}\n\n// ─── 内部:构建 raw client 与 token manager ──────────────────────────────────\n\n/**\n * 仅 auth 存在时创建 TokenManager;刷新沿用自定义头、客户端标识与同一加密 fetch。\n * @param config - 已选定的客户端配置。\n * @param transport - 可选已创建的加密会话。\n * @returns 本实例管理器;没有 auth 时为 undefined,不自行启用认证。\n *\n * @example 模块内部上下文\n * ```ts\n * const manager = buildTokenManager({ baseUrl: 'https://api.example.com/api/v1', auth: { storage: createMemoryTokenStorage() }, headers: { 'x-app-channel': 'web' } }, undefined)\n * await manager?.clear()\n * ```\n */\nfunction buildTokenManager(config: ApiClientConfig, transport: TransportClient | undefined): TokenManager | undefined {\n if (!config.auth)\n return undefined\n\n const fetchFn = transport?.encryptedFetch ?? config.fetch ?? globalThis.fetch.bind(globalThis)\n // 默认使用 httpOnly cookie 存储(推荐方案:refresh token 由服务端管理,防 XSS)。\n // SSR / Node.js 测试场景请显式传入 `apiClient.tokenStorage.memory()`。\n // 原生客户端显式传入平台安全存储,浏览器不持久化 Token 到 Web Storage。\n const storage = config.auth.storage ?? createHttpOnlyCookieTokenStorage()\n const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, '')\n const refreshPath = config.auth.refreshPath ?? DEFAULT_REFRESH_PATH\n const refreshUrl = `${baseUrl}${refreshPath.startsWith('/') ? refreshPath : `/${refreshPath}`}`\n\n // 刷新沿用客户端标识与自定义头(包括原生 body 通道标识),并复用同一加密 fetch。\n const refreshFetch: typeof fetch = async (input, init) => {\n const configured = typeof config.headers === 'function' ? await config.headers() : config.headers ?? {}\n const headers = new Headers(configured)\n if (!headers.has('accept-language'))\n headers.set('accept-language', core.i18n.getGlobalLocale())\n headers.set('x-request-client', config.clientName ?? DEFAULT_CLIENT_NAME)\n new Headers(init?.headers).forEach((value, name) => headers.set(name, value))\n return fetchFn(input, { ...init, headers })\n }\n const manager = createTokenManager(storage, refreshUrl, refreshFetch, config.auth.onRefreshFailed, config.auth.refreshTimeoutMs)\n if (config.auth.onTokenRefreshed) {\n manager.onTokenRefreshed(config.auth.onTokenRefreshed)\n }\n return manager\n}\n\n/**\n * 根据显式 transport 配置创建会话;首次 encryptedFetch 才进行实际密钥协商。\n * @param config - 客户端配置及已初始化 crypto。\n * @returns 新会话或 undefined;调用方在失败/close 时 destroy。\n *\n * @example 模块内部上下文\n * ```ts\n * const session = buildTransportSession({ baseUrl: 'https://api.example.com/api/v1' })\n * // 未配置 transport 时不产生会话;正常明文请求仍由实际 fetch 发送。\n * session?.destroy()\n * ```\n */\nfunction buildTransportSession(config: ApiClientConfig): TransportClient | undefined {\n if (!config.transport)\n return undefined\n const baseFetch = config.fetch ?? globalThis.fetch.bind(globalThis)\n const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, '')\n const path = config.transport.keyExchangePath ?? '/_hai/key-exchange'\n const keyExchangeUrl = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`\n return config.transport.crypto.transport.createClient({ keyExchangeUrl, fetch: baseFetch, maxPlaintextBytes: config.transport.maxPlaintextBytes })\n}\n\n/**\n * 以同一 contract 构造 OpenAPILink 与 JSON typed client,统一头组装、超时和认证重试。\n * @param contract - 实际 procedure 契约。\n * @param config - 目标地址与平台 fetch。\n * @param tokenManager - 可选 Token 管理器。\n * @param transport - 可选加密会话。\n * @returns oRPC JSON client;模块外层代理将调用异常映射为失败 HaiResult。\n *\n * @example 模块内部上下文\n * ```ts\n * const contract = apiContract.create({ iam: apiContract.iam })\n * const raw = buildRawClient(contract, { baseUrl: 'https://api.example.com/api/v1' }, undefined, undefined)\n * // 构造不发送请求;业务调用通过外层 ApiClient 代理。\n * ```\n */\nfunction buildRawClient<TContract extends AnyContractRouter>(\n contract: TContract,\n config: ApiClientConfig,\n tokenManager: TokenManager | undefined,\n transport: TransportClient | undefined,\n): JsonifiedClient<ContractRouterClient<TContract>> {\n const link = new OpenAPILink<Record<PropertyKey, unknown>>(contract, {\n url: config.baseUrl,\n headers: async options => buildHeaders(config, tokenManager, getObservation(options.context)?.requestId),\n fetch: async (request, init, options) => {\n const record = getObservation(options.context)\n if (record) {\n record.method = request.method\n const url = new URL(request.url)\n url.search = ''\n url.hash = ''\n record.url = core.sanitize.sanitizeUrl(url.toString())\n }\n const response = await fetchWithRefresh(request, init, config, tokenManager, transport, record)\n if (record) {\n record.status = response.status\n const traceId = response.headers.get('x-trace-id') ?? response.headers.get('x-request-id')\n if (traceId && /^[\\w.:-]{1,128}$/.test(traceId))\n record.requestId = traceId\n }\n return response\n },\n })\n return createORPCClient<JsonifiedClient<ContractRouterClient<TContract>>>(link)\n}\n\n/** 内部 context 属性只由逻辑调用建立,不能从业务输入读取。 */\nfunction getObservation(context: Record<PropertyKey, unknown>): MutableRequestRecord | undefined {\n const record = context[REQUEST_OBSERVATION]\n return record && typeof record === 'object' && 'requestId' in record ? record as MutableRequestRecord : undefined\n}\n\n/**\n * 合并静态/动态配置头与客户端标识,读取可用 access token 后设置 Bearer 头。\n * @param config - 用户请求头与客户端标识。\n * @param tokenManager - 可选管理器;读取安全存储失败向上游传播。\n * @returns 单次请求的头快照,不改变调用方配置对象。\n *\n * @example 模块内部上下文\n * ```ts\n * const headers = await buildHeaders({ baseUrl: 'https://api.example.com/api/v1', clientName: 'orders-web', headers: () => ({ 'x-app-channel': 'web' }) }, undefined)\n * const channel = headers['x-app-channel']\n * ```\n */\nasync function buildHeaders(\n config: ApiClientConfig,\n tokenManager: TokenManager | undefined,\n requestId?: string,\n): Promise<Record<string, string>> {\n const configured = typeof config.headers === 'function' ? await config.headers() : config.headers ?? {}\n const headers = new Headers(configured)\n if (!headers.has('accept-language'))\n headers.set('accept-language', core.i18n.getGlobalLocale())\n const accessToken = await tokenManager?.storage.getAccessToken()\n return {\n ...Object.fromEntries(headers),\n 'x-request-client': config.clientName ?? DEFAULT_CLIENT_NAME,\n 'x-request-id': requestId ?? core.context.get().requestId ?? core.id.uuid(),\n ...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}),\n }\n}\n\n/**\n * 发送前克隆一次性请求体;HTTP 401 或有效失败包络的 401 才尝试刷新并最多重试一次。\n * 登录/注册/找回密码与 refresh 入口保留原始失败,不递归刷新;重试保留 body 并替换 Bearer 头。\n * @param request - 尚未消费的 Request。\n * @param init - oRPC 提供的重定向选项。\n * @param init.redirect - 可选的 fetch 重定向策略,原样传给首次请求及重试。\n * @param config - 请求与刷新配置。\n * @param tokenManager - 缺省时直接返回首次响应。\n * @param transport - 刷新与重试共用的可选加密会话。\n * @returns 最终 HTTP Response;不保证远端非幂等副作用没有发生。\n *\n * @example 模块内部上下文\n * ```ts\n * const config: ApiClientConfig = { baseUrl: 'https://api.example.com/api/v1', fetch: async () => Response.json({ success: true, data: {} }) }\n * const response = await fetchWithRefresh(new Request('https://api.example.com/api/v1/auth/me'), {}, config, undefined, undefined)\n * const status = response.status\n * ```\n */\nasync function fetchWithRefresh(\n request: Request,\n init: { redirect?: Request['redirect'] },\n config: ApiClientConfig,\n tokenManager: TokenManager | undefined,\n transport: TransportClient | undefined,\n observation?: MutableRequestRecord,\n): Promise<Response> {\n // Fetch Request 的 body 是一次性可读流;为了支持 401 后重发 POST/PUT 等带 body 的请求,\n // 必须在首次发送前 `clone()` 一份。即使没有 401,clone 后的副本也只会被 GC 回收,无副作用。\n const retryRequest = tokenManager ? request.clone() : undefined\n if (observation)\n observation.attempts++\n const response = await fetchWithTimeout(request, init, config, transport)\n if (!tokenManager || !retryRequest)\n return response\n\n // 登录失败必须保留原始错误;refresh 本身也不能再次触发自动刷新。\n const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, '')\n const refreshPath = config.auth?.refreshPath ?? DEFAULT_REFRESH_PATH\n const excludedPaths = [...AUTH_ENTRY_PATHS, refreshPath]\n if (excludedPaths.some(path => new URL(`${baseUrl}${path.startsWith('/') ? path : `/${path}`}`).pathname === new URL(request.url).pathname))\n return response\n if (response.status !== 401 && !await isUnauthorizedResult(response))\n return response\n\n const tokens = await tokenManager.refresh()\n if (!tokens)\n return response\n\n const headers = new Headers(retryRequest.headers)\n headers.set('authorization', `Bearer ${tokens.accessToken}`)\n if (observation)\n observation.attempts++\n return fetchWithTimeout(new Request(retryRequest, { headers }), init, config, transport)\n}\n\n/**\n * 只从 HTTP 200 JSON 的有效失败 HaiResult 识别 httpStatus=401;克隆响应避免消费原正文。\n * @param response - 原始 HTTP 响应。\n * @returns 是否满足认证失效包络;非法 JSON/字段缺失留给原解析路径处理。\n *\n * @example 模块内部上下文\n * ```ts\n * const response = Response.json({ success: false, error: { code: 'hai:iam:001', message: 'Session expired', httpStatus: 401 } })\n * const unauthorized = await isUnauthorizedResult(response)\n * const originalBody = await response.json()\n * // 克隆检查没有消耗原响应正文。\n * ```\n */\nasync function isUnauthorizedResult(response: Response): Promise<boolean> {\n if (response.status !== 200 || !response.headers.get('content-type')?.includes('application/json'))\n return false\n try {\n const body: unknown = await response.clone().json()\n if (!body || typeof body !== 'object' || !('success' in body) || body.success !== false\n || !('error' in body) || !body.error || typeof body.error !== 'object') {\n return false\n }\n return 'httpStatus' in body.error && body.error.httpStatus === 401\n && 'code' in body.error && typeof body.error.code === 'string'\n && 'message' in body.error && typeof body.error.message === 'string'\n }\n catch {\n // 非 JSON/非法包络交由既有客户端解析路径报告,不掩盖为认证失败。\n return false\n }\n}\n\n/**\n * 把请求超时和调用方 signal 连接到本次 fetch;finally 清除本次超时计时器。\n * @param request - 含调用方取消信号的 Request。\n * @param init - oRPC 重定向设置。\n * @param init.redirect - 可选的 fetch 重定向策略,原样交给本次实际 fetch。\n * @param config - 超时与 fetch 配置。\n * @param transport - 可选加密 fetch,优先于原始 fetch。\n * @returns HTTP 响应;超时/取消异常由外层 procedure 映射,不能视为远端回滚。\n *\n * @example 模块内部上下文\n * ```ts\n * const request = new Request('https://api.example.com/api/v1/auth/me')\n * const response = await fetchWithTimeout(request, {}, { baseUrl: 'https://api.example.com/api/v1', timeout: 1000, fetch: async () => new Response(null, { status: 204 }) }, undefined)\n * const status = response.status\n * ```\n */\nasync function fetchWithTimeout(\n request: Request,\n init: { redirect?: Request['redirect'] },\n config: ApiClientConfig,\n transport: TransportClient | undefined,\n): Promise<Response> {\n // 启用传输加密时,用 encryptedFetch 作为实际 fetch;\n // 它会首次请求前自动完成密钥协商,后续请求复用会话。\n const fetchFn = transport?.encryptedFetch ?? config.fetch ?? globalThis.fetch.bind(globalThis)\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n // 保留调用方原始 signal 的中断能力:上游主动 abort 也能控制超时 controller。\n const userSignal = request.signal\n if (userSignal) {\n if (userSignal.aborted) {\n controller.abort()\n }\n else {\n userSignal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n }\n try {\n return await fetchFn(new Request(request, { signal: controller.signal }), init)\n }\n finally {\n clearTimeout(timeoutId)\n }\n}\n\n/**\n * 将 oRPC 状态、AbortError 与其它传输异常映射为稳定客户端错误;保留 oRPC code/status/data 详情。\n * @param error - procedure 抛出的未知异常。\n * @returns 失败 HaiResult,不包含虚构的成功数据。\n *\n * @example 模块内部上下文\n * ```ts\n * const result = mapClientError(new DOMException('Request aborted', 'AbortError'))\n * if (!result.success) { const code = result.error.code }\n * ```\n */\nfunction mapClientError(error: unknown) {\n const time = error instanceof TransportTimeError ? error : error instanceof Error && error.cause instanceof TransportTimeError ? error.cause : undefined\n if (time)\n return err(HaiApiClientError.VALIDATION_FAILED, time.message, { transportCode: time.code })\n const capacity = error instanceof TransportBodyTooLargeError ? error : error instanceof Error && error.cause instanceof TransportBodyTooLargeError ? error.cause : undefined\n if (capacity)\n return err(HaiApiClientError.PAYLOAD_TOO_LARGE, capacity.message, { direction: capacity.direction, stage: capacity.stage, limitBytes: capacity.limitBytes, actualBytes: capacity.actualBytes })\n if (error instanceof ORPCError) {\n // 保留 ORPCError 的原始 code / status / data,供上层区分具体业务异常(不仅是 HTTP 状态码)。\n const detail = { orpcCode: error.code, status: error.status, data: error.data }\n if (error.status === 413)\n return err(HaiApiClientError.PAYLOAD_TOO_LARGE, error.message, detail)\n if (error.status === 400 || error.status === 422)\n return err(HaiApiClientError.VALIDATION_FAILED, error.message, detail)\n if (error.status === 401)\n return err(HaiApiClientError.UNAUTHORIZED, error.message, detail)\n if (error.status === 403)\n return err(HaiApiClientError.FORBIDDEN, error.message, detail)\n if (error.status === 404)\n return err(HaiApiClientError.NOT_FOUND, error.message, detail)\n return err(HaiApiClientError.SERVER_ERROR, error.message, detail)\n }\n if (error instanceof DOMException && error.name === 'AbortError') {\n return err(HaiApiClientError.TIMEOUT, apiClientM('apiClient_timeout'), error)\n }\n return err(HaiApiClientError.NETWORK_ERROR, apiClientM('apiClient_networkError'), error)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/api-client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.72",
|
|
5
5
|
"description": "Hai Framework universal HTTP client with Bearer token and API contract support.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"node": "^22.23.4 || ^24.22.1 || ^26.12.1"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@h-ai/api-contract": "0.1.0-alpha.
|
|
27
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
28
|
-
"@h-ai/crypto": "0.1.0-alpha.
|
|
26
|
+
"@h-ai/api-contract": "0.1.0-alpha.72",
|
|
27
|
+
"@h-ai/core": "0.1.0-alpha.72",
|
|
28
|
+
"@h-ai/crypto": "0.1.0-alpha.72",
|
|
29
29
|
"@orpc/client": "^1.15.5",
|
|
30
30
|
"@orpc/contract": "^1.15.5",
|
|
31
31
|
"@orpc/openapi-client": "^1.15.5",
|