@h-ai/kit 0.1.0-alpha.73 → 0.1.0-alpha.75
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 +10 -0
- package/README.ja.md +12 -0
- package/README.md +12 -0
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +4 -4
- package/dist/{chunk-UTCS6FR7.js → chunk-726DCCEK.js} +63 -55
- package/dist/chunk-726DCCEK.js.map +1 -0
- package/dist/{chunk-OVRZIDCD.js → chunk-KRR7QGMN.js} +11 -5
- package/dist/chunk-KRR7QGMN.js.map +1 -0
- package/dist/{chunk-3L5QSJ3E.js → chunk-QPJ5AWXX.js} +41 -11
- package/dist/chunk-QPJ5AWXX.js.map +1 -0
- package/dist/{chunk-Y2YUA3VR.js → chunk-TYZ3S2AY.js} +5 -4
- package/dist/chunk-TYZ3S2AY.js.map +1 -0
- package/dist/{chunk-2PWGJCWX.js → chunk-X3NGOGT6.js} +3 -3
- package/dist/{chunk-2PWGJCWX.js.map → chunk-X3NGOGT6.js.map} +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +14 -10
- package/dist/index.js.map +1 -1
- package/dist/{kit-a2a-types-ChZCbX3t.d.ts → kit-a2a-types-BPPh1x_a.d.ts} +1 -1
- package/dist/{kit-client-auth-CSB3wDSI.d.ts → kit-client-auth-DZOoQxlp.d.ts} +15 -3
- package/dist/{kit-crypto-types-Cwj9bprV.d.ts → kit-crypto-types-DgdeRenR.d.ts} +2 -0
- package/dist/{kit-types-B6rjOQWx.d.ts → kit-types-yogJY1i6.d.ts} +2 -0
- package/dist/modules/a2a/index.d.ts +3 -3
- package/dist/modules/a2a/index.js +2 -2
- package/dist/modules/crypto/index.d.ts +2 -2
- package/dist/modules/crypto/index.js +2 -2
- package/package.json +10 -10
- package/dist/chunk-3L5QSJ3E.js.map +0 -1
- package/dist/chunk-OVRZIDCD.js.map +0 -1
- package/dist/chunk-UTCS6FR7.js.map +0 -1
- package/dist/chunk-Y2YUA3VR.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -218,6 +218,8 @@ Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD c
|
|
|
218
218
|
|
|
219
219
|
Set `transport.maxPlaintextBytes` for both request and response JSON plaintext, measured in UTF-8 bytes: default 1 MiB, integer range 1 KiB–64 MiB. Clients learn the server limit during key exchange. `resolveTransportBodyLimits()` derives encrypted/proxy bytes as twice the plaintext limit plus 32 KiB. Handshake, public-key, header and replay constraints remain independent. Oversized streams are cancelled and readers released: requests return 413; oversized responses return an encrypted 500 capacity error, recognized as `TransportBodyTooLargeError` / `hai:crypto:070`. [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
|
|
220
220
|
|
|
221
|
+
Ordinary routes retain `transport.maxPlaintextBytes` (auto/omitted: 1 MiB). Declare Feedback report intake, evidence, attachments and ZIP download capacity separately using `routePlaintextBytes` and `feedback.budget.maxTransportBytes`; `*` matches one complete path segment and explicit host route values take precedence. Handshakes, adapter-node and proxies use the maximum route capacity. HTTP adapters admit at most 2 explicitly declared material requests above 1 MiB and a separate 16 ordinary business requests, releasing admission after the entire response or failure. Busy admission returns 503 / `hai:crypto:072`; business writes are not automatically replayed.
|
|
222
|
+
|
|
221
223
|
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.
|
|
222
224
|
|
|
223
225
|
#### Shared transport configuration with `_kit.yml`
|
|
@@ -354,10 +356,18 @@ Export kit.auth.createApi(iam).createCaptcha as POST /api/auth/captcha; kit.auth
|
|
|
354
356
|
|
|
355
357
|
See policy, defaults and recovery boundaries in [IAM](../iam/README.en.md).
|
|
356
358
|
|
|
359
|
+
## Recovering an expired session in place
|
|
360
|
+
|
|
361
|
+
Set `kit.client.create({ recoverSession })` to sign in again without discarding the current page. Use UI `createSessionRecovery` / `SessionRecoveryDialog`: configure the public policy and original user in the root layout, pass `recovery.recover` as the callback, provide standard authentication actions, and cancel on teardown. IAM checks login `expectedUserId` before issuing a session; captcha, lockout, password renewal and access rules remain active. Only an explicitly authentication-rejected request is retried once. Cancellation, 5xx, timeouts and ambiguous writes are not replayed. Sensitive-operation proofs from an old session may need a new confirmation. Standard authentication actions disable recovery for their own calls. Custom authentication requests should pass `{ recoverSession: false }` as the third apiFetch argument to avoid recursive dialogs. terminalType (web/app/desktop/api; default web) selects a session group and does not attest hardware identity.
|
|
362
|
+
|
|
357
363
|
## Page exit session policy
|
|
358
364
|
|
|
359
365
|
`session.endOnPageHide` defaults to `false`. Hosts call `actions.bindPageExit({ enabled: config.session.endOnPageHide })` and invoke its returned cleanup on unmount; standard admin/H5/fullstack templates already bind it. A non-BFCache `pagehide` (including reload and full document navigation) makes a best effort POST to `/auth/session/close`, revoking the held access and related refresh through the existing CSRF/encrypted transport with `keepalive` and no automatic refresh. This response does not clear cookies/local credentials, avoiding a late old response overwriting a new login. Explicit logout still revokes and clears current credentials normally. Browsers may omit page exit requests; configure server `idleTimeout`/`absoluteTimeout` as appropriate. Immediate revocation on browser close is not guaranteed.
|
|
360
366
|
|
|
367
|
+
## Transport capacity and module budgets
|
|
368
|
+
|
|
369
|
+
For a sensitive call, supply `reauthorize: () => sensitiveActions.authorize(operation)` in the third Kit apiFetch argument or typed-client options.context. After session rotation/recovery or an explicit proof rejection, obtain a new purpose-bound proof before one retry. Cancellation sends no further write; without a callback, an old proof is never replayed.
|
|
370
|
+
|
|
361
371
|
## Development and testing
|
|
362
372
|
|
|
363
373
|
```bash
|
package/README.ja.md
CHANGED
|
@@ -224,6 +224,8 @@ transport は既定で同一オリジンの `/api/*`、SvelteKit の `__data.jso
|
|
|
224
224
|
|
|
225
225
|
`transport.maxPlaintextBytes` でリクエストとレスポンスの JSON 平文容量を共通設定します。単位は UTF-8 バイト、既定値は 1 MiB、有効範囲は 1 KiB~64 MiB の整数です。クライアントは鍵交換で上限を取得し、`resolveTransportBodyLimits()` が暗号文とプロキシ容量(平文の 2 倍 + 32 KiB)を算出します。鍵交換、公開鍵、認証ヘッダー、防リプレイ制約は独立です。超過時は元ストリームを停止して reader を解放します。要求は 413、応答は暗号化された 500 容量エラーとなり、`TransportBodyTooLargeError` / `hai:crypto:070` で識別できます。 [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
|
|
226
226
|
|
|
227
|
+
通常業務は `transport.maxPlaintextBytes`(auto/省略は 1 MiB)を維持します。Feedback の受理・証拠・添付・ZIP ダウンロードは `routePlaintextBytes` と `feedback.budget.maxTransportBytes` で別途宣言します。`*` は完全な1パス区間のみ一致し、ホストの明示値が優先です。ハンドシェイク、adapter-node、プロキシは経路の最大値を使用します。HTTP アダプターは個別宣言された 1 MiB 超の資料経路を最大2件、通常業務を別枠で16件まで許可し、応答完了・失敗時に解放します。混雑は503 / `hai:crypto:072` を返し、業務書込は自動再送しません。
|
|
228
|
+
|
|
227
229
|
serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
|
|
228
230
|
|
|
229
231
|
#### `_kit.yml` による共通 transport 設定
|
|
@@ -352,10 +354,20 @@ kit.auth.createApi(iam).createCaptcha を POST /api/auth/captcha として公開
|
|
|
352
354
|
|
|
353
355
|
方針・既定値・復旧範囲は次を参照: [IAM](../iam/README.ja.md)。
|
|
354
356
|
|
|
357
|
+
## 期限切れセッションの画面内復帰
|
|
358
|
+
|
|
359
|
+
`kit.client.create({ recoverSession })` によって現在の画面と入力を保持して再ログインできます。UI の `createSessionRecovery` / `SessionRecoveryDialog` を使い、ルートで公開方針と元の利用者を設定し、`recovery.recover` と標準認証 actions を渡して破棄時に cancel します。IAM は発行前に `expectedUserId` を確認し、通常の CAPTCHA・ロック・改密・アクセス規則を適用します。認証拒否が明確な要求だけ1回続行でき、取消・5xx・タイムアウト・結果不明の書込は再送しません。旧セッションの重要操作証明は再確認が必要な場合があります。標準認証 actions は自身の復帰再試行を無効にします。独自認証は apiFetch の第3引数 `{ recoverSession: false }` で再帰を防ぎます。terminalType(web/app/desktop/api、既定 web)は分類用で機器認証ではありません。
|
|
360
|
+
|
|
355
361
|
## ページ離脱時のセッション方針
|
|
356
362
|
|
|
357
363
|
`session.endOnPageHide` の既定値は `false` です。ホストは `actions.bindPageExit({ enabled: config.session.endOnPageHide })` を呼び、破棄時に戻り値の解除関数を実行します。標準 admin/H5/fullstack テンプレートは接続済みです。BFCache 以外の `pagehide`(再読み込み、文書全体の移動を含む)で POST `/auth/session/close` を最善努力で送信し、保持する access と関連 refresh を失効します。既存の CSRF/暗号化転送を使用し、`keepalive` を付け、自動更新しません。古い応答が新しいログインを上書きしないよう Cookie/ローカル資格情報を消去しません。明示的ログアウトは通常どおり失効と消去を行います。ブラウザーが離脱要求を送らない場合もあるため、業務に応じてサーバーの `idleTimeout`/`absoluteTimeout` を併用してください。閉じた瞬間の失効は保証できません。
|
|
358
364
|
|
|
365
|
+
## 転送容量とモジュール予算
|
|
366
|
+
|
|
367
|
+
設定 transport.maxPlaintextBytes は 1 KiB~64 MiB の明示整数または auto を受け付けます。auto/省略は undefined に正規化され、要件なしの既定は 1 MiB、フィードバックのホストでは crypto.resolveTransportBodyLimits の requiredPlaintextBytes で統合します。明示上限の不足は起動/梱包時の失敗とし、上書きしません。プロキシ・adapter-node・ハンドシェイク・実行時の予算を一致させ、添付・展開・行数の制限は独立して検証します。
|
|
368
|
+
|
|
369
|
+
重要操作では `reauthorize: () => sensitiveActions.authorize(operation)` を Kit apiFetch の第3引数、または typed client の options.context に渡します。セッション更新・復帰または明確な証明拒否の後、新しい用途証明で1回だけ続行します。取消時は追加の書込を送らず、コールバックがなければ旧証明を再送しません。
|
|
370
|
+
|
|
359
371
|
## 開発とテスト
|
|
360
372
|
|
|
361
373
|
```bash
|
package/README.md
CHANGED
|
@@ -224,6 +224,8 @@ transport 默认作用于同源 `/api/*` endpoint 与 SvelteKit `__data.json`
|
|
|
224
224
|
|
|
225
225
|
业务容量通过 `transport.maxPlaintextBytes` 统一配置:默认 1 MiB,整数范围 1 KiB~64 MiB,请求和响应共用 UTF-8 明文字节预算。客户端从握手自动获取上限,密文与部署代理预算由 `resolveTransportBodyLimits()` 推导(两倍明文 + 32 KiB)。握手、公钥、认证头和防重放约束独立;超限取消原流并释放 reader,请求 413、响应为加密的 500 容量错误,客户端识别 `TransportBodyTooLargeError` / `hai:crypto:070`。 [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
|
|
226
226
|
|
|
227
|
+
普通业务仍使用 `transport.maxPlaintextBytes`(auto/省略为 1 MiB);Feedback 的报告受理、证据、附件及 ZIP 下载路由通过 `routePlaintextBytes` 单独声明 `feedback.budget.maxTransportBytes`。路径中的 `*` 仅匹配一个完整路径段;宿主显式路由值优先。握手、adapter-node 与代理使用各路由最大容量,普通业务不会随材料预算扩大。HTTP 适配器为单独声明且大于 1 MiB 的材料路由保留最多 2 个槽,普通业务另有 16 个槽,完整响应/失败后释放;忙碌返回 503 / `hai:crypto:072`,不自动重放业务写入。
|
|
228
|
+
|
|
227
229
|
serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
|
|
228
230
|
|
|
229
231
|
#### 使用 `_kit.yml` 统一 transport 配置
|
|
@@ -352,10 +354,20 @@ audit 已初始化时,createHandle 为业务 audit.log 绑定已验证会话
|
|
|
352
354
|
|
|
353
355
|
策略、默认值和恢复边界见 [IAM](../iam/README.md)。
|
|
354
356
|
|
|
357
|
+
## 原地恢复过期会话
|
|
358
|
+
|
|
359
|
+
配置 `kit.client.create({ recoverSession })` 回调可在服务端明确拒绝会话时原地重新登录并保留页面输入。使用 `@h-ai/ui` 的 `createSessionRecovery` / `SessionRecoveryDialog`:根布局绑定公开配置与原用户,回调设为 `recovery.recover`,弹窗使用标准认证 actions,销毁时 `cancel()`。登录的 `expectedUserId` 由 IAM 限定原主体;常规验证码、锁定、改密和登录限制照常执行。只对明确未认证拒绝的原请求续接一次;取消、5xx、超时和结果不明的网络写入不重放。敏感操作证明绑定旧会话时可能需要再次确认,不复用到新会话。标准认证动作显式禁用自身的恢复重试,避免错误密码递归打开弹窗。自定义认证请求应传第三个参数 `{ recoverSession: false }`。终端分组通过 terminalType(web/app/desktop/api,默认 web)传入;它不是硬件身份认证。
|
|
360
|
+
|
|
355
361
|
## 页面离开会话策略
|
|
356
362
|
|
|
357
363
|
`session.endOnPageHide` 默认 `false`。启用后宿主调用 `actions.bindPageExit({ enabled: config.session.endOnPageHide })` 并在卸载时调用返回的清理函数;标准 admin/H5/fullstack 模板已经接入。非 BFCache 的 `pagehide`(也包括刷新、整页导航)通过 POST `/auth/session/close` 尽力撤销当前 access 及关联 refresh,沿用 CSRF/传输加密,使用 `keepalive` 且不自动刷新会话。该响应不清 Cookie/本地凭据,避免旧响应覆盖下一次登录。显式退出仍使用正常 logout,可靠撤销并清理当前凭据。浏览器可能不发送页面离开请求,因此必须按业务配合服务端 `idleTimeout`/`absoluteTimeout`;不能据此保证关闭即撤销。
|
|
358
364
|
|
|
365
|
+
## 传输容量与模块预算
|
|
366
|
+
|
|
367
|
+
配置 `transport.maxPlaintextBytes` 支持 1 KiB~64 MiB 的显式整数或 `auto`。auto/省略在解析后为 undefined:无模块需求时仍为 1 MiB,反馈宿主使用 crypto.resolveTransportBodyLimits 的 requiredPlaintextBytes 统一推导。显式上限不足时启动/打包失败,不能被反馈覆盖;代理、adapter-node、握手与运行时使用一致预算。业务附件、解压和数据行数上限继续独立校验。
|
|
368
|
+
|
|
369
|
+
敏感调用通过本次 `reauthorize: () => sensitiveActions.authorize(operation)` 重新确认用途:Kit 放 apiFetch 第三参数,typed client 放 options.context。会话刷新/恢复或明确的再认证拒绝后仅续发一次;取消不写入,未提供回调不重放旧证明。
|
|
370
|
+
|
|
359
371
|
## 开发测试
|
|
360
372
|
|
|
361
373
|
```bash
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-
|
|
2
|
-
export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, f as KitConfig, g as KitConfigInput, h as KitConfigSchema, j as KitServerConfig, k as KitServerConfigInput, l as KitTransportConfig, m as KitTransportConfigInput, r as resolveKitConfig, n as resolveKitTransportConfig } from './kit-client-auth-
|
|
1
|
+
import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-DZOoQxlp.js';
|
|
2
|
+
export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig, f as KitConfig, g as KitConfigInput, h as KitConfigSchema, j as KitServerConfig, k as KitServerConfigInput, l as KitTransportConfig, m as KitTransportConfigInput, r as resolveKitConfig, n as resolveKitTransportConfig } from './kit-client-auth-DZOoQxlp.js';
|
|
3
3
|
import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-DEzmzE5T.js';
|
|
4
|
-
export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, h as HandleConfig, i as HookCryptoConfig, M as Middleware, j as MiddlewareContext, R as RateLimitConfig, k as RouteGuard, S as SessionData, l as SessionLike, T as TransportEncryptionOptions } from './kit-types-
|
|
4
|
+
export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, h as HandleConfig, i as HookCryptoConfig, M as Middleware, j as MiddlewareContext, R as RateLimitConfig, k as RouteGuard, S as SessionData, l as SessionLike, T as TransportEncryptionOptions } from './kit-types-yogJY1i6.js';
|
|
5
5
|
import '@h-ai/api-contract';
|
|
6
6
|
import '@h-ai/core';
|
|
7
7
|
import '@h-ai/crypto';
|
package/dist/browser.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { defineCrud } from './chunk-
|
|
2
|
-
export { KitConfigSchema, resolveKitConfig, resolveKitTransportConfig } from './chunk-
|
|
3
|
-
import { clearBrowserToken, setBrowserToken, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient } from './chunk-
|
|
1
|
+
import { defineCrud } from './chunk-TYZ3S2AY.js';
|
|
2
|
+
export { KitConfigSchema, resolveKitConfig, resolveKitTransportConfig } from './chunk-TYZ3S2AY.js';
|
|
3
|
+
import { clearBrowserToken, setBrowserToken, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient } from './chunk-QPJ5AWXX.js';
|
|
4
4
|
import './chunk-XC7I2L3G.js';
|
|
5
|
-
import { setAllModulesLocale } from './chunk-
|
|
5
|
+
import { setAllModulesLocale } from './chunk-KRR7QGMN.js';
|
|
6
6
|
|
|
7
7
|
// src/browser.ts
|
|
8
8
|
var kit = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH, shouldHandleTransportPath, shouldExcludeTransportPath } from './chunk-XC7I2L3G.js';
|
|
2
|
-
import { kitM } from './chunk-
|
|
3
|
-
import { TRANSPORT_PROTOCOL, authenticateTransportRequest, TransportTimeError, createTransportErrorResponse, TransportBodyTooLargeError, readTransportBody, TRANSPORT_BODY_LIMITS, isValidSM2PublicKey } from '@h-ai/crypto';
|
|
2
|
+
import { kitM } from './chunk-KRR7QGMN.js';
|
|
3
|
+
import { createTransportBodyPolicy, TRANSPORT_PROTOCOL, authenticateTransportRequest, TransportTimeError, createTransportErrorResponse, TransportBodyTooLargeError, readTransportBody, TRANSPORT_BODY_LIMITS, isValidSM2PublicKey } from '@h-ai/crypto';
|
|
4
4
|
|
|
5
5
|
var ENCRYPTABLE_RESPONSE_CONTENT_TYPES = ["application/json", "text/sveltekit-data"];
|
|
6
6
|
function transportEncryptionMiddleware(config) {
|
|
@@ -9,10 +9,11 @@ function transportEncryptionMiddleware(config) {
|
|
|
9
9
|
}
|
|
10
10
|
const keyExchangePath = config.keyExchangePath ?? DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH;
|
|
11
11
|
const excludePaths = config.excludePaths ?? [];
|
|
12
|
+
const policy = createTransportBodyPolicy(config.maxPlaintextBytes, config.routePlaintextBytes);
|
|
12
13
|
const result = config.crypto.transport.createServer({
|
|
13
14
|
serverKeyRing: config.serverKeyRing,
|
|
14
15
|
keyStore: config.keyStore,
|
|
15
|
-
maxPlaintextBytes:
|
|
16
|
+
maxPlaintextBytes: policy.maximum.plaintextBytes
|
|
16
17
|
});
|
|
17
18
|
if (!result.success) {
|
|
18
19
|
return createUnavailableTransportMiddleware(keyExchangePath, excludePaths);
|
|
@@ -33,60 +34,67 @@ function transportEncryptionMiddleware(config) {
|
|
|
33
34
|
const clientId = event.request.headers.get(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER);
|
|
34
35
|
if (!clientId)
|
|
35
36
|
return jsonError(kitM("kit_transportClientIdRequired"), 400);
|
|
36
|
-
|
|
37
|
+
const lease = policy.acquire(pathname);
|
|
38
|
+
if (!lease)
|
|
39
|
+
return jsonError(kitM("kit_transportBusy"), 503);
|
|
37
40
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
let requestContext;
|
|
42
|
+
try {
|
|
43
|
+
const verified = await authenticateTransportRequest(manager, event.request, lease.limits);
|
|
44
|
+
if (!verified.success) {
|
|
45
|
+
if (verified.error.cause instanceof TransportTimeError && verified.error.cause.context)
|
|
46
|
+
return createTransportErrorResponse(verified.error.cause, { manager, context: verified.error.cause.context });
|
|
47
|
+
return jsonError(kitM("kit_transportDecryptFailed"), 400);
|
|
48
|
+
}
|
|
49
|
+
requestContext = verified.data.context;
|
|
50
|
+
if (event.request.method !== "GET" && event.request.method !== "HEAD")
|
|
51
|
+
replaceRequestBody(event, verified.data.plaintext);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof TransportBodyTooLargeError)
|
|
54
|
+
return createTransportErrorResponse(error);
|
|
55
|
+
return jsonError(kitM("kit_transportInvalidPayload"), error instanceof RangeError ? 413 : 400);
|
|
43
56
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const bodyless = event.request.method === "HEAD" || isEmptyResponse(response);
|
|
54
|
-
if (bodyless)
|
|
55
|
-
void response.body?.cancel().catch(() => {
|
|
56
|
-
});
|
|
57
|
-
const contentType = response.headers.get("Content-Type") ?? "";
|
|
58
|
-
if (!bodyless && !canEncryptResponseContentType(contentType)) {
|
|
59
|
-
void response.body?.cancel().catch(() => {
|
|
60
|
-
});
|
|
61
|
-
return jsonError(kitM("kit_transportEncryptFailed"), 500);
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
const responseBody = bodyless ? "" : await readTransportBody(response.body, {
|
|
65
|
-
maxBytes: manager.bodyLimits.plaintextBytes,
|
|
66
|
-
direction: "response",
|
|
67
|
-
signal: event.request.signal
|
|
68
|
-
});
|
|
69
|
-
const headers = new Headers(response.headers);
|
|
70
|
-
headers.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER);
|
|
71
|
-
headers.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
|
|
72
|
-
headers.delete("Content-Length");
|
|
73
|
-
headers.delete("Content-Encoding");
|
|
74
|
-
const encryptedPayload = await manager.encryptResponse(requestContext, responseBody, response.status);
|
|
75
|
-
if (!encryptedPayload.success) {
|
|
57
|
+
const response = await next();
|
|
58
|
+
const bodyless = event.request.method === "HEAD" || isEmptyResponse(response);
|
|
59
|
+
if (bodyless)
|
|
60
|
+
void response.body?.cancel().catch(() => {
|
|
61
|
+
});
|
|
62
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
63
|
+
if (!bodyless && !canEncryptResponseContentType(contentType)) {
|
|
64
|
+
void response.body?.cancel().catch(() => {
|
|
65
|
+
});
|
|
76
66
|
return jsonError(kitM("kit_transportEncryptFailed"), 500);
|
|
77
67
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
headers
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
68
|
+
try {
|
|
69
|
+
const responseBody = bodyless ? "" : await readTransportBody(response.body, {
|
|
70
|
+
maxBytes: lease.limits.plaintextBytes,
|
|
71
|
+
direction: "response",
|
|
72
|
+
signal: event.request.signal
|
|
73
|
+
});
|
|
74
|
+
const headers = new Headers(response.headers);
|
|
75
|
+
headers.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER);
|
|
76
|
+
headers.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
|
|
77
|
+
headers.delete("Content-Length");
|
|
78
|
+
headers.delete("Content-Encoding");
|
|
79
|
+
const encryptedPayload = await manager.encryptResponse(requestContext, responseBody, response.status);
|
|
80
|
+
if (!encryptedPayload.success) {
|
|
81
|
+
return jsonError(kitM("kit_transportEncryptFailed"), 500);
|
|
82
|
+
}
|
|
83
|
+
headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE);
|
|
84
|
+
if (bodyless)
|
|
85
|
+
headers.set(TRANSPORT_PROTOCOL.ENVELOPE_HEADER, JSON.stringify(encryptedPayload.data));
|
|
86
|
+
return new Response(bodyless ? null : JSON.stringify(encryptedPayload.data), {
|
|
87
|
+
status: response.status,
|
|
88
|
+
statusText: response.statusText,
|
|
89
|
+
headers
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof TransportBodyTooLargeError)
|
|
93
|
+
return createTransportErrorResponse(error, { manager, context: requestContext });
|
|
94
|
+
return jsonError(kitM("kit_transportEncryptFailed"), 500);
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
lease.release();
|
|
90
98
|
}
|
|
91
99
|
};
|
|
92
100
|
}
|
|
@@ -168,5 +176,5 @@ function replaceRequestBody(event, body) {
|
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
export { transportEncryptionMiddleware };
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
172
|
-
//# sourceMappingURL=chunk-
|
|
179
|
+
//# sourceMappingURL=chunk-726DCCEK.js.map
|
|
180
|
+
//# sourceMappingURL=chunk-726DCCEK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/modules/crypto/kit-transport-middleware.ts"],"names":[],"mappings":"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","file":"chunk-726DCCEK.js","sourcesContent":["/**\n * @h-ai/kit — 传输加密中间件\n *\n * SvelteKit 中间件,自动对请求/响应进行传输加密解密。\n * @module kit-transport-middleware\n */\n\nimport type { TransportEncryptionManager, TransportMessageContext } from '@h-ai/crypto'\nimport type { Middleware } from '../../kit-types.js'\nimport type { TransportEncryptionConfig } from './kit-crypto-types.js'\nimport { authenticateTransportRequest, createTransportBodyPolicy, createTransportErrorResponse, isValidSM2PublicKey, readTransportBody, TRANSPORT_BODY_LIMITS, TRANSPORT_PROTOCOL, TransportBodyTooLargeError, TransportTimeError } from '@h-ai/crypto'\nimport { kitM } from '../../kit-i18n.js'\nimport { DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH, shouldExcludeTransportPath, shouldHandleTransportPath } from './kit-transport-paths.js'\n\nconst ENCRYPTABLE_RESPONSE_CONTENT_TYPES = ['application/json', 'text/sveltekit-data']\n\n/**\n * 创建传输加密中间件\n *\n * @param config - 传输加密配置\n * @returns SvelteKit 中间件\n * @example 模块内部上下文\n * ```ts\n * function install(config: TransportEncryptionConfig) {\n * return transportEncryptionMiddleware(config) // 受管密钥环、共享 keyStore 与 crypto 生命周期由应用管理。\n * }\n * ```\n */\nexport function transportEncryptionMiddleware(config: TransportEncryptionConfig): Middleware {\n // 未启用时直接透传\n if (!config.enabled) {\n return async (_context, next) => next()\n }\n\n const keyExchangePath = config.keyExchangePath ?? DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH\n const excludePaths = config.excludePaths ?? []\n\n const policy = createTransportBodyPolicy(config.maxPlaintextBytes, config.routePlaintextBytes)\n\n // 初始化传输加密管理器\n const result = config.crypto.transport.createServer({\n serverKeyRing: config.serverKeyRing,\n keyStore: config.keyStore,\n maxPlaintextBytes: policy.maximum.plaintextBytes,\n })\n if (!result.success) {\n // 安全策略:transport 已启用但服务端管理器不可用时,受保护路由必须 fail-closed,禁止明文透传。\n return createUnavailableTransportMiddleware(keyExchangePath, excludePaths)\n }\n const manager = result.data\n\n return async (context, next) => {\n const { event } = context\n const pathname = getRequestPathname(event.request)\n\n // 密钥交换端点:委托给 keyExchangeHandler\n if (pathname === keyExchangePath && event.request.method === 'POST') {\n return handleKeyExchange(manager, event.request)\n }\n\n // 仅对同源 API endpoint 与 SvelteKit __data 请求启用 transport;\n // 页面文档、静态资源等仍透传,避免首页 HTML 被误拦截。\n if (!shouldHandleTransportPath(pathname, keyExchangePath)) {\n return next()\n }\n\n // 排除路径不做加解密\n if (shouldExcludeTransportPath(pathname, excludePaths, keyExchangePath)) {\n return next()\n }\n\n // 请求头中无 X-Client-Id 时的处理\n const clientId = event.request.headers.get(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER)\n if (!clientId)\n return jsonError(kitM('kit_transportClientIdRequired'), 400)\n\n const lease = policy.acquire(pathname)\n if (!lease)\n return jsonError(kitM('kit_transportBusy'), 503)\n try {\n // ── 解密请求 ──\n // 不依赖可伪造/省略的 Content-Length/Type 判空,非 null 流必须进入实际内容检查。\n let requestContext: TransportMessageContext\n try {\n const verified = await authenticateTransportRequest(manager, event.request, lease.limits)\n if (!verified.success) {\n if (verified.error.cause instanceof TransportTimeError && verified.error.cause.context)\n return createTransportErrorResponse(verified.error.cause, { manager, context: verified.error.cause.context })\n return jsonError(kitM('kit_transportDecryptFailed'), 400)\n }\n requestContext = verified.data.context\n if (event.request.method !== 'GET' && event.request.method !== 'HEAD')\n replaceRequestBody(event, verified.data.plaintext)\n }\n catch (error) {\n if (error instanceof TransportBodyTooLargeError)\n return createTransportErrorResponse(error)\n return jsonError(kitM('kit_transportInvalidPayload'), error instanceof RangeError ? 413 : 400)\n }\n\n // ── 执行后续中间件/端点 ──\n const response = await next()\n\n // ── 加密响应 ──\n\n // 只允许可安全加密的响应体离开受保护路由;无法加密时 fail-closed,禁止明文泄露。\n const bodyless = event.request.method === 'HEAD' || isEmptyResponse(response)\n if (bodyless)\n void response.body?.cancel().catch(() => {})\n\n const contentType = response.headers.get('Content-Type') ?? ''\n if (!bodyless && !canEncryptResponseContentType(contentType)) {\n void response.body?.cancel().catch(() => {})\n return jsonError(kitM('kit_transportEncryptFailed'), 500)\n }\n try {\n const responseBody = bodyless\n ? ''\n : await readTransportBody(response.body, {\n maxBytes: lease.limits.plaintextBytes,\n direction: 'response',\n signal: event.request.signal,\n })\n const headers = new Headers(response.headers)\n headers.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)\n headers.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER)\n headers.delete('Content-Length')\n headers.delete('Content-Encoding')\n\n const encryptedPayload = await manager.encryptResponse(requestContext, responseBody, response.status)\n if (!encryptedPayload.success) {\n return jsonError(kitM('kit_transportEncryptFailed'), 500)\n }\n headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)\n if (bodyless)\n headers.set(TRANSPORT_PROTOCOL.ENVELOPE_HEADER, JSON.stringify(encryptedPayload.data))\n return new Response(bodyless ? null : JSON.stringify(encryptedPayload.data), {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n catch (error) {\n if (error instanceof TransportBodyTooLargeError)\n return createTransportErrorResponse(error, { manager, context: requestContext })\n return jsonError(kitM('kit_transportEncryptFailed'), 500)\n }\n }\n finally { lease.release() }\n }\n}\n\nfunction canEncryptResponseContentType(contentType: string): boolean {\n return ENCRYPTABLE_RESPONSE_CONTENT_TYPES.some(type => contentType.includes(type))\n}\n\nfunction createUnavailableTransportMiddleware(\n keyExchangePath: string,\n excludePaths: string[],\n): Middleware {\n return async (context, next) => {\n const pathname = getRequestPathname(context.event.request)\n if (pathname === keyExchangePath) {\n return jsonError(kitM('kit_transportKeyGenerationFailed'), 500)\n }\n if (!shouldHandleTransportPath(pathname, keyExchangePath)) {\n return next()\n }\n if (shouldExcludeTransportPath(pathname, excludePaths, keyExchangePath)) {\n return next()\n }\n return jsonError(kitM('kit_transportKeyGenerationFailed'), 500)\n }\n}\n\nfunction isEmptyResponse(response: Response): boolean {\n if ([204, 304].includes(response.status))\n return true\n // Content-Length 不能证明响应流为空,避免明文伪装零长度后绕过加密。\n return response.body === null\n}\n\nfunction getRequestPathname(request: Request): string {\n return new URL(request.url).pathname\n}\n\nfunction jsonError(message: string, status: number): Response {\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\n/**\n * 处理密钥交换 POST 请求\n *\n * 接收客户端公钥,注册并返回服务端公钥与 clientId。\n *\n * @param manager - 传输加密管理器\n * @param request - 原始 Request\n * @returns JSON Response\n */\nasync function handleKeyExchange(\n manager: TransportEncryptionManager,\n request: Request,\n): Promise<Response> {\n let body: unknown\n try {\n body = JSON.parse(await readTransportBody(request.body, {\n maxBytes: TRANSPORT_BODY_LIMITS.keyExchangeBytes,\n signal: request.signal,\n }))\n }\n catch (error) {\n return jsonError(kitM('kit_transportInvalidPayload'), error instanceof RangeError ? 413 : 400)\n }\n if (!body || typeof body !== 'object' || !('clientPublicKey' in body)\n || !isValidSM2PublicKey(body.clientPublicKey)\n || new TextEncoder().encode(body.clientPublicKey).byteLength > TRANSPORT_BODY_LIMITS.publicKeyBytes) {\n return jsonError(kitM('kit_transportInvalidPayload'), 400)\n }\n\n try {\n const serverPublicKey = manager.getServerPublicKey()\n if (new TextEncoder().encode(serverPublicKey).byteLength > TRANSPORT_BODY_LIMITS.publicKeyBytes)\n return jsonError(kitM('kit_transportKeyExchangeFailed'), 500)\n const clientId = await manager.registerClientKey(body.clientPublicKey)\n\n return new Response(\n JSON.stringify({ serverKeyId: manager.getServerKeyId(), serverPublicKey, clientId, maxPlaintextBytes: manager.bodyLimits.plaintextBytes }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n }\n catch {\n return jsonError(kitM('kit_transportKeyExchangeFailed'), 500)\n }\n}\n\n/**\n * 用于替换已验证请求正文的最小可写容器。\n * @example 模块内部上下文\n * ```ts\n * const holder: RequestHolder = { request: new Request('https://app.example.com/api/articles', { method: 'POST', body: '{}' }) }\n * replaceRequestBody(holder, JSON.stringify({ title: 'Example' }))\n * ```\n */\ninterface RequestHolder {\n request: Request\n}\n\n/**\n * 重建 SvelteKit 事件中的 Request。\n *\n * 有界认证会消费密文 body;只将成功认证的明文恢复给后续端点。\n *\n * @param event SvelteKit 请求事件\n * @param body 新请求体\n */\nfunction replaceRequestBody(event: RequestHolder, body: string): void {\n const headers = new Headers(event.request.headers)\n headers.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)\n headers.delete('Content-Length')\n headers.delete('Content-Encoding')\n headers.set('Content-Type', 'application/json; charset=utf-8')\n const newRequest = new Request(event.request.url, {\n method: event.request.method,\n headers,\n body,\n signal: event.request.signal,\n })\n Object.defineProperty(event, 'request', {\n value: newRequest,\n writable: true,\n configurable: true,\n })\n}\n"]}
|
|
@@ -53,7 +53,9 @@ var en_US_default = {
|
|
|
53
53
|
kit_requiredPermissions: "Required permissions: {permissions}",
|
|
54
54
|
kit_idRequired: "ID is required",
|
|
55
55
|
kit_rateLimitWindowInvalid: "The rate limit window must be an integer from 1 to 2147483647 milliseconds",
|
|
56
|
-
kit_rateLimitMemoryCapacity: "The development memory rate limit store is full; use a shared backend"
|
|
56
|
+
kit_rateLimitMemoryCapacity: "The development memory rate limit store is full; use a shared backend",
|
|
57
|
+
kit_transportBusy: "Material processing is busy. Please retry shortly.",
|
|
58
|
+
kit_feedbackUnavailable: "Feedback is temporarily unavailable. Business features remain available."
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
// messages/ja-JP.json
|
|
@@ -107,7 +109,9 @@ var ja_JP_default = {
|
|
|
107
109
|
kit_requiredPermissions: "\u5FC5\u8981\u306A\u6A29\u9650: {permissions}",
|
|
108
110
|
kit_idRequired: "ID\u306F\u5FC5\u9808\u3067\u3059",
|
|
109
111
|
kit_rateLimitWindowInvalid: "\u30EC\u30FC\u30C8\u5236\u9650\u306E\u671F\u9593\u306F1\u301C2147483647\u30DF\u30EA\u79D2\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
110
|
-
kit_rateLimitMemoryCapacity: "\u958B\u767A\u7528\u30E1\u30E2\u30EA\u30FC\u30EC\u30FC\u30C8\u5236\u9650\u30B9\u30C8\u30A2\u304C\u6E80\u676F\u3067\u3059\u3002\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
112
|
+
kit_rateLimitMemoryCapacity: "\u958B\u767A\u7528\u30E1\u30E2\u30EA\u30FC\u30EC\u30FC\u30C8\u5236\u9650\u30B9\u30C8\u30A2\u304C\u6E80\u676F\u3067\u3059\u3002\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
113
|
+
kit_transportBusy: "\u8CC7\u6599\u306E\u51E6\u7406\u304C\u6DF7\u307F\u5408\u3063\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
114
|
+
kit_feedbackUnavailable: "\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u306F\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u696D\u52D9\u6A5F\u80FD\u306F\u5F15\u304D\u7D9A\u304D\u5229\u7528\u3067\u304D\u307E\u3059\u3002"
|
|
111
115
|
};
|
|
112
116
|
|
|
113
117
|
// messages/zh-CN.json
|
|
@@ -161,7 +165,9 @@ var zh_CN_default = {
|
|
|
161
165
|
kit_requiredPermissions: "\u9700\u8981\u6743\u9650\uFF1A{permissions}",
|
|
162
166
|
kit_idRequired: "ID \u4E0D\u80FD\u4E3A\u7A7A",
|
|
163
167
|
kit_rateLimitWindowInvalid: "\u9650\u6D41\u7A97\u53E3\u5FC5\u987B\u4E3A 1 \u81F3 2147483647 \u7684\u6574\u6570\u6BEB\u79D2",
|
|
164
|
-
kit_rateLimitMemoryCapacity: "\u5F00\u53D1\u5185\u5B58\u9650\u6D41\u5BB9\u91CF\u5DF2\u6EE1\uFF0C\u8BF7\u4F7F\u7528\u5171\u4EAB\u540E\u7AEF"
|
|
168
|
+
kit_rateLimitMemoryCapacity: "\u5F00\u53D1\u5185\u5B58\u9650\u6D41\u5BB9\u91CF\u5DF2\u6EE1\uFF0C\u8BF7\u4F7F\u7528\u5171\u4EAB\u540E\u7AEF",
|
|
169
|
+
kit_transportBusy: "\u6750\u6599\u5904\u7406\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
170
|
+
kit_feedbackUnavailable: "\u95EE\u9898\u53CD\u9988\u6682\u4E0D\u53EF\u7528\uFF0C\u4E1A\u52A1\u529F\u80FD\u53EF\u7EE7\u7EED\u4F7F\u7528"
|
|
165
171
|
};
|
|
166
172
|
|
|
167
173
|
// src/kit-i18n.ts
|
|
@@ -171,5 +177,5 @@ function setAllModulesLocale(locale) {
|
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
export { kitM, setAllModulesLocale };
|
|
174
|
-
//# sourceMappingURL=chunk-
|
|
175
|
-
//# sourceMappingURL=chunk-
|
|
180
|
+
//# sourceMappingURL=chunk-KRR7QGMN.js.map
|
|
181
|
+
//# sourceMappingURL=chunk-KRR7QGMN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/kit-i18n.ts"],"names":[],"mappings":";;;;;AAAA,IAAA,aAAA,GAAA;AAAA,EACE,iBAAA,EAAqB,2BAAA;AAAA,EACrB,iBAAA,EAAqB,2BAAA;AAAA,EACrB,sBAAA,EAA0B,0BAAA;AAAA,EAC1B,OAAA,EAAW,iDAAA;AAAA,EACX,gBAAA,EAAoB,cAAA;AAAA,EACpB,aAAA,EAAiB,4CAAA;AAAA,EACjB,qBAAA,EAAyB,wEAAA;AAAA,EACzB,mBAAA,EAAuB,+BAAA;AAAA,EACvB,oBAAA,EAAwB,gCAAA;AAAA,EACxB,wBAAA,EAA4B,oBAAA;AAAA,EAC5B,qBAAA,EAAyB,2CAAA;AAAA,EACzB,0BAAA,EAA8B,0BAAA;AAAA,EAC9B,mBAAA,EAAuB,8BAAA;AAAA,EACvB,iBAAA,EAAqB,mBAAA;AAAA,EACrB,gCAAA,EAAoC,4CAAA;AAAA,EACpC,8BAAA,EAAkC,sBAAA;AAAA,EAClC,0BAAA,EAA8B,6BAAA;AAAA,EAC9B,6BAAA,EAAiC,iCAAA;AAAA,EACjC,6BAAA,EAAiC,iCAAA;AAAA,EACjC,0BAAA,EAA8B,6BAAA;AAAA,EAC9B,2BAAA,EAA+B,2BAAA;AAAA,EAC/B,8BAAA,EAAkC,qBAAA;AAAA,EAClC,6BAAA,EAAiC,+EAAA;AAAA,EACjC,cAAA,EAAkB,wBAAA;AAAA,EAClB,gBAAA,EAAoB,yBAAA;AAAA,EACpB,gBAAA,EAAoB,eAAA;AAAA,EACpB,oBAAA,EAAwB,oBAAA;AAAA,EACxB,iBAAA,EAAqB,uBAAA;AAAA,EACrB,oBAAA,EAAwB,mBAAA;AAAA,EACxB,sBAAA,EAA0B,wBAAA;AAAA,EAC1B,qBAAA,EAAyB,sBAAA;AAAA,EACzB,yBAAA,EAA6B,oBAAA;AAAA,EAC7B,uBAAA,EAA2B,iCAAA;AAAA,EAC3B,uBAAA,EAA2B,gCAAA;AAAA,EAC3B,uBAAA,EAA2B,8BAAA;AAAA,EAC3B,uBAAA,EAA2B,6BAAA;AAAA,EAC3B,sBAAA,EAA0B,+BAAA;AAAA,EAC1B,sBAAA,EAA0B,8BAAA;AAAA,EAC1B,sBAAA,EAA0B,8BAAA;AAAA,EAC1B,oBAAA,EAAwB,6BAAA;AAAA,EACxB,mBAAA,EAAuB,6BAAA;AAAA,EACvB,iBAAA,EAAqB,mBAAA;AAAA,EACrB,kBAAA,EAAsB,oBAAA;AAAA,EACtB,kBAAA,EAAsB,uBAAA;AAAA,EACtB,kBAAA,EAAsB,kBAAA;AAAA,EACtB,iBAAA,EAAqB,yBAAA;AAAA,EACrB,uBAAA,EAA2B,qCAAA;AAAA,EAC3B,cAAA,EAAkB,gBAAA;AAAA,EAClB,0BAAA,EAA8B,4EAAA;AAAA,EAC9B,2BAAA,EAA+B,uEAAA;AAAA,EAC/B,iBAAA,EAAqB,oDAAA;AAAA,EACrB,uBAAA,EAA2B;AAC7B,CAAA;;;ACrDA,IAAA,aAAA,GAAA;AAAA,EACE,iBAAA,EAAqB,2DAAA;AAAA,EACrB,iBAAA,EAAqB,kHAAA;AAAA,EACrB,sBAAA,EAA0B,wGAAA;AAAA,EAC1B,OAAA,EAAW,iDAAA;AAAA,EACX,gBAAA,EAAoB,wDAAA;AAAA,EACpB,aAAA,EAAiB,wIAAA;AAAA,EACjB,qBAAA,EAAyB,mKAAA;AAAA,EACzB,mBAAA,EAAuB,0FAAA;AAAA,EACvB,oBAAA,EAAwB,0FAAA;AAAA,EACxB,wBAAA,EAA4B,4DAAA;AAAA,EAC5B,qBAAA,EAAyB,gLAAA;AAAA,EACzB,0BAAA,EAA8B,sFAAA;AAAA,EAC9B,mBAAA,EAAuB,wGAAA;AAAA,EACvB,iBAAA,EAAqB,8DAAA;AAAA,EACrB,gCAAA,EAAoC,kGAAA;AAAA,EACpC,8BAAA,EAAkC,4FAAA;AAAA,EAClC,0BAAA,EAA8B,gFAAA;AAAA,EAC9B,6BAAA,EAAiC,sFAAA;AAAA,EACjC,6BAAA,EAAiC,gFAAA;AAAA,EACjC,0BAAA,EAA8B,0EAAA;AAAA,EAC9B,2BAAA,EAA+B,gFAAA;AAAA,EAC/B,8BAAA,EAAkC,8DAAA;AAAA,EAClC,6BAAA,EAAiC,yKAAA;AAAA,EACjC,cAAA,EAAkB,4FAAA;AAAA,EAClB,gBAAA,EAAoB,4CAAA;AAAA,EACpB,gBAAA,EAAoB,0EAAA;AAAA,EACpB,oBAAA,EAAwB,0EAAA;AAAA,EACxB,iBAAA,EAAqB,wDAAA;AAAA,EACrB,oBAAA,EAAwB,sFAAA;AAAA,EACxB,sBAAA,EAA0B,wDAAA;AAAA,EAC1B,qBAAA,EAAyB,wDAAA;AAAA,EACzB,yBAAA,EAA6B,0EAAA;AAAA,EAC7B,uBAAA,EAA2B,qFAAA;AAAA,EAC3B,uBAAA,EAA2B,qFAAA;AAAA,EAC3B,uBAAA,EAA2B,qFAAA;AAAA,EAC3B,uBAAA,EAA2B,qFAAA;AAAA,EAC3B,sBAAA,EAA0B,yEAAA;AAAA,EAC1B,sBAAA,EAA0B,+EAAA;AAAA,EAC1B,sBAAA,EAA0B,qFAAA;AAAA,EAC1B,oBAAA,EAAwB,qFAAA;AAAA,EACxB,mBAAA,EAAuB,oHAAA;AAAA,EACvB,iBAAA,EAAqB,6EAAA;AAAA,EACrB,kBAAA,EAAsB,8EAAA;AAAA,EACtB,kBAAA,EAAsB,sFAAA;AAAA,EACtB,kBAAA,EAAsB,4FAAA;AAAA,EACtB,iBAAA,EAAqB,+CAAA;AAAA,EACrB,uBAAA,EAA2B,+CAAA;AAAA,EAC3B,cAAA,EAAkB,kCAAA;AAAA,EAClB,0BAAA,EAA8B,mKAAA;AAAA,EAC9B,2BAAA,EAA+B,sOAAA;AAAA,EAC/B,iBAAA,EAAqB,wMAAA;AAAA,EACrB,uBAAA,EAA2B;AAC7B,CAAA;;;ACrDA,IAAA,aAAA,GAAA;AAAA,EACE,iBAAA,EAAqB,0CAAA;AAAA,EACrB,iBAAA,EAAqB,uDAAA;AAAA,EACrB,sBAAA,EAA0B,+BAAA;AAAA,EAC1B,OAAA,EAAW,iDAAA;AAAA,EACX,gBAAA,EAAoB,oBAAA;AAAA,EACpB,aAAA,EAAiB,sEAAA;AAAA,EACjB,qBAAA,EAAyB,qGAAA;AAAA,EACzB,mBAAA,EAAuB,sCAAA;AAAA,EACvB,oBAAA,EAAwB,qCAAA;AAAA,EACxB,wBAAA,EAA4B,yBAAA;AAAA,EAC5B,qBAAA,EAAyB,0EAAA;AAAA,EACzB,0BAAA,EAA8B,kDAAA;AAAA,EAC9B,mBAAA,EAAuB,4CAAA;AAAA,EACvB,iBAAA,EAAqB,0BAAA;AAAA,EACrB,gCAAA,EAAoC,oEAAA;AAAA,EACpC,8BAAA,EAAkC,kDAAA;AAAA,EAClC,0BAAA,EAA8B,sCAAA;AAAA,EAC9B,6BAAA,EAAiC,kDAAA;AAAA,EACjC,6BAAA,EAAiC,kDAAA;AAAA,EACjC,0BAAA,EAA8B,sCAAA;AAAA,EAC9B,2BAAA,EAA+B,4CAAA;AAAA,EAC/B,8BAAA,EAAkC,sCAAA;AAAA,EAClC,6BAAA,EAAiC,iIAAA;AAAA,EACjC,cAAA,EAAkB,sCAAA;AAAA,EAClB,gBAAA,EAAoB,sCAAA;AAAA,EACpB,gBAAA,EAAoB,0BAAA;AAAA,EACpB,oBAAA,EAAwB,gCAAA;AAAA,EACxB,iBAAA,EAAqB,4CAAA;AAAA,EACrB,oBAAA,EAAwB,sCAAA;AAAA,EACxB,sBAAA,EAA0B,gCAAA;AAAA,EAC1B,qBAAA,EAAyB,sCAAA;AAAA,EACzB,yBAAA,EAA6B,4CAAA;AAAA,EAC7B,uBAAA,EAA2B,mDAAA;AAAA,EAC3B,uBAAA,EAA2B,mDAAA;AAAA,EAC3B,uBAAA,EAA2B,4CAAA;AAAA,EAC3B,uBAAA,EAA2B,4CAAA;AAAA,EAC3B,sBAAA,EAA0B,uCAAA;AAAA,EAC1B,sBAAA,EAA0B,uCAAA;AAAA,EAC1B,sBAAA,EAA0B,gCAAA;AAAA,EAC1B,oBAAA,EAAwB,gCAAA;AAAA,EACxB,mBAAA,EAAuB,8DAAA;AAAA,EACvB,iBAAA,EAAqB,kDAAA;AAAA,EACrB,kBAAA,EAAsB,2CAAA;AAAA,EACtB,kBAAA,EAAsB,4CAAA;AAAA,EACtB,kBAAA,EAAsB,sCAAA;AAAA,EACtB,iBAAA,EAAqB,uCAAA;AAAA,EACrB,uBAAA,EAA2B,6CAAA;AAAA,EAC3B,cAAA,EAAkB,6BAAA;AAAA,EAClB,0BAAA,EAA8B,+FAAA;AAAA,EAC9B,2BAAA,EAA+B,8GAAA;AAAA,EAC/B,iBAAA,EAAqB,0EAAA;AAAA,EACrB,uBAAA,EAA2B;AAC7B,CAAA;;;ACtBO,IAAM,IAAA,GACT,IAAA,CAAK,IAAA,CAAK,mBAAA,CAAmC,EAAE,OAAA,EAAS,aAAA,EAAc,OAAA,EAAS,aAAA,EAAc,OAAA,EAAS,aAAA,EAAc;AAgBjH,SAAS,oBAAoB,MAAA,EAAsB;AACxD,EAAA,IAAA,CAAK,IAAA,CAAK,gBAAgB,MAAM,CAAA;AAClC","file":"chunk-KRR7QGMN.js","sourcesContent":["{\n \"kit_a2aAuthFailed\": \"A2A authentication failed\",\n \"kit_a2aParseError\": \"Invalid JSON request body\",\n \"kit_a2aCardUnavailable\": \"Agent card not available\",\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"kit_unauthorized\": \"Unauthorized\",\n \"kit_forbidden\": \"Forbidden: missing {permission} permission\",\n \"kit_authNotConfigured\": \"kit.auth is not configured: pass auth.operations in kit.createHandle()\",\n \"kit_csrfTokenFailed\": \"Failed to generate CSRF token\",\n \"kit_csrfVerifyFailed\": \"CSRF token verification failed\",\n \"kit_csrfValidationFailed\": \"Invalid CSRF token\",\n \"kit_rateLimitExceeded\": \"Too many requests, please try again later\",\n \"kit_unsupportedContentType\": \"Unsupported content type\",\n \"kit_parseBodyFailed\": \"Failed to parse request body\",\n \"kit_encryptFailed\": \"Encryption failed\",\n \"kit_transportKeyGenerationFailed\": \"Transport encryption key generation failed\",\n \"kit_transportClientKeyNotFound\": \"Client key not found\",\n \"kit_transportEncryptFailed\": \"Transport encryption failed\",\n \"kit_transportEncryptKeyFailed\": \"Symmetric key encryption failed\",\n \"kit_transportDecryptKeyFailed\": \"Symmetric key decryption failed\",\n \"kit_transportDecryptFailed\": \"Transport decryption failed\",\n \"kit_transportInvalidPayload\": \"Invalid encrypted payload\",\n \"kit_transportKeyExchangeFailed\": \"Key exchange failed\",\n \"kit_transportClientIdRequired\": \"Missing X-Client-Id header, key exchange is required for transport encryption\",\n \"kit_signFailed\": \"Request signing failed\",\n \"kit_authRequired\": \"Authentication required\",\n \"kit_accessDenied\": \"Access denied\",\n \"kit_resourceNotFound\": \"Resource not found\",\n \"kit_internalError\": \"Internal server error\",\n \"kit_validationFailed\": \"Validation failed\",\n \"kit_validationRequired\": \"This field is required\",\n \"kit_validationInvalid\": \"Invalid input format\",\n \"kit_validationInvalidType\": \"Invalid field type\",\n \"kit_validationStringMin\": \"Enter at least {min} characters\",\n \"kit_validationStringMax\": \"Enter at most {max} characters\",\n \"kit_validationNumberMin\": \"Value must be at least {min}\",\n \"kit_validationNumberMax\": \"Value must be at most {max}\",\n \"kit_validationArrayMin\": \"Select at least {min} item(s)\",\n \"kit_validationArrayMax\": \"Select at most {max} item(s)\",\n \"kit_validationTooSmall\": \"Value must be at least {min}\",\n \"kit_validationTooBig\": \"Value must be at most {max}\",\n \"kit_validationEmail\": \"Enter a valid email address\",\n \"kit_validationUrl\": \"Enter a valid URL\",\n \"kit_validationUuid\": \"Enter a valid UUID\",\n \"kit_validationEnum\": \"Select a valid option\",\n \"kit_noGuardsPassed\": \"No guards passed\",\n \"kit_requiredRoles\": \"Required roles: {roles}\",\n \"kit_requiredPermissions\": \"Required permissions: {permissions}\",\n \"kit_idRequired\": \"ID is required\",\n \"kit_rateLimitWindowInvalid\": \"The rate limit window must be an integer from 1 to 2147483647 milliseconds\",\n \"kit_rateLimitMemoryCapacity\": \"The development memory rate limit store is full; use a shared backend\",\n \"kit_transportBusy\": \"Material processing is busy. Please retry shortly.\",\n \"kit_feedbackUnavailable\": \"Feedback is temporarily unavailable. Business features remain available.\"\n}\n","{\n \"kit_a2aAuthFailed\": \"A2A認証に失敗しました\",\n \"kit_a2aParseError\": \"リクエスト本文が有効なJSONではありません\",\n \"kit_a2aCardUnavailable\": \"エージェントカードを利用できません\",\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"kit_unauthorized\": \"認証されていません\",\n \"kit_forbidden\": \"アクセスが拒否されました: {permission}権限がありません\",\n \"kit_authNotConfigured\": \"kit.authが設定されていません。kit.createHandle()にauth.operationsを渡してください\",\n \"kit_csrfTokenFailed\": \"CSRFトークンの生成に失敗しました\",\n \"kit_csrfVerifyFailed\": \"CSRFトークンの検証に失敗しました\",\n \"kit_csrfValidationFailed\": \"CSRFトークンが無効です\",\n \"kit_rateLimitExceeded\": \"リクエストが多すぎます。しばらくしてから再試行してください\",\n \"kit_unsupportedContentType\": \"未対応のコンテンツタイプです\",\n \"kit_parseBodyFailed\": \"リクエスト本文の解析に失敗しました\",\n \"kit_encryptFailed\": \"暗号化に失敗しました\",\n \"kit_transportKeyGenerationFailed\": \"通信暗号化鍵の生成に失敗しました\",\n \"kit_transportClientKeyNotFound\": \"クライアント鍵が見つかりません\",\n \"kit_transportEncryptFailed\": \"通信の暗号化に失敗しました\",\n \"kit_transportEncryptKeyFailed\": \"対称鍵の暗号化に失敗しました\",\n \"kit_transportDecryptKeyFailed\": \"対称鍵の復号に失敗しました\",\n \"kit_transportDecryptFailed\": \"通信の復号に失敗しました\",\n \"kit_transportInvalidPayload\": \"暗号化ペイロードが無効です\",\n \"kit_transportKeyExchangeFailed\": \"鍵交換に失敗しました\",\n \"kit_transportClientIdRequired\": \"X-Client-Idヘッダーがありません。通信暗号化には鍵交換が必要です\",\n \"kit_signFailed\": \"リクエストの署名に失敗しました\",\n \"kit_authRequired\": \"認証が必要です\",\n \"kit_accessDenied\": \"アクセスが拒否されました\",\n \"kit_resourceNotFound\": \"リソースが見つかりません\",\n \"kit_internalError\": \"サーバー内部エラー\",\n \"kit_validationFailed\": \"入力内容の検証に失敗しました\",\n \"kit_validationRequired\": \"この項目は必須です\",\n \"kit_validationInvalid\": \"入力形式が無効です\",\n \"kit_validationInvalidType\": \"フィールドの型が無効です\",\n \"kit_validationStringMin\": \"{min}文字以上で入力してください\",\n \"kit_validationStringMax\": \"{max}文字以内で入力してください\",\n \"kit_validationNumberMin\": \"{min}以上の値を入力してください\",\n \"kit_validationNumberMax\": \"{max}以下の値を入力してください\",\n \"kit_validationArrayMin\": \"{min}件以上選択してください\",\n \"kit_validationArrayMax\": \"{max}件以内で選択してください\",\n \"kit_validationTooSmall\": \"{min}以上の値を入力してください\",\n \"kit_validationTooBig\": \"{max}以下の値を入力してください\",\n \"kit_validationEmail\": \"有効なメールアドレスを入力してください\",\n \"kit_validationUrl\": \"有効なURLを入力してください\",\n \"kit_validationUuid\": \"有効なUUIDを入力してください\",\n \"kit_validationEnum\": \"有効な選択肢を選んでください\",\n \"kit_noGuardsPassed\": \"どのガードも通過しませんでした\",\n \"kit_requiredRoles\": \"必要なロール: {roles}\",\n \"kit_requiredPermissions\": \"必要な権限: {permissions}\",\n \"kit_idRequired\": \"IDは必須です\",\n \"kit_rateLimitWindowInvalid\": \"レート制限の期間は1〜2147483647ミリ秒の整数で指定してください\",\n \"kit_rateLimitMemoryCapacity\": \"開発用メモリーレート制限ストアが満杯です。共有バックエンドを使用してください\",\n \"kit_transportBusy\": \"資料の処理が混み合っています。しばらくしてから再試行してください。\",\n \"kit_feedbackUnavailable\": \"フィードバックは一時的に利用できません。業務機能は引き続き利用できます。\"\n}\n","{\n \"kit_a2aAuthFailed\": \"A2A 身份认证失败\",\n \"kit_a2aParseError\": \"请求体不是有效的 JSON\",\n \"kit_a2aCardUnavailable\": \"Agent Card 不可用\",\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"kit_unauthorized\": \"未授权\",\n \"kit_forbidden\": \"权限不足:缺少 {permission} 权限\",\n \"kit_authNotConfigured\": \"kit.auth 未配置:请在 kit.createHandle() 中传入 auth.operations\",\n \"kit_csrfTokenFailed\": \"生成 CSRF Token 失败\",\n \"kit_csrfVerifyFailed\": \"CSRF Token 验证失败\",\n \"kit_csrfValidationFailed\": \"CSRF Token 无效\",\n \"kit_rateLimitExceeded\": \"请求过于频繁,请稍后再试\",\n \"kit_unsupportedContentType\": \"不支持的内容类型\",\n \"kit_parseBodyFailed\": \"请求体解析失败\",\n \"kit_encryptFailed\": \"加密失败\",\n \"kit_transportKeyGenerationFailed\": \"传输加密密钥对生成失败\",\n \"kit_transportClientKeyNotFound\": \"客户端密钥未找到\",\n \"kit_transportEncryptFailed\": \"传输加密失败\",\n \"kit_transportEncryptKeyFailed\": \"对称密钥加密失败\",\n \"kit_transportDecryptKeyFailed\": \"对称密钥解密失败\",\n \"kit_transportDecryptFailed\": \"传输解密失败\",\n \"kit_transportInvalidPayload\": \"无效的加密载荷\",\n \"kit_transportKeyExchangeFailed\": \"密钥交换失败\",\n \"kit_transportClientIdRequired\": \"缺少 X-Client-Id 请求头,传输加密必须先完成密钥交换\",\n \"kit_signFailed\": \"请求签名失败\",\n \"kit_authRequired\": \"需要身份认证\",\n \"kit_accessDenied\": \"拒绝访问\",\n \"kit_resourceNotFound\": \"资源未找到\",\n \"kit_internalError\": \"内部服务器错误\",\n \"kit_validationFailed\": \"数据验证失败\",\n \"kit_validationRequired\": \"此字段必填\",\n \"kit_validationInvalid\": \"输入格式无效\",\n \"kit_validationInvalidType\": \"字段类型不正确\",\n \"kit_validationStringMin\": \"至少输入 {min} 个字符\",\n \"kit_validationStringMax\": \"最多输入 {max} 个字符\",\n \"kit_validationNumberMin\": \"数值不能小于 {min}\",\n \"kit_validationNumberMax\": \"数值不能大于 {max}\",\n \"kit_validationArrayMin\": \"至少选择 {min} 项\",\n \"kit_validationArrayMax\": \"最多选择 {max} 项\",\n \"kit_validationTooSmall\": \"不能小于 {min}\",\n \"kit_validationTooBig\": \"不能大于 {max}\",\n \"kit_validationEmail\": \"请输入有效的邮箱地址\",\n \"kit_validationUrl\": \"请输入有效的网址\",\n \"kit_validationUuid\": \"请输入有效的 UUID\",\n \"kit_validationEnum\": \"请选择有效选项\",\n \"kit_noGuardsPassed\": \"没有守卫通过\",\n \"kit_requiredRoles\": \"需要角色:{roles}\",\n \"kit_requiredPermissions\": \"需要权限:{permissions}\",\n \"kit_idRequired\": \"ID 不能为空\",\n \"kit_rateLimitWindowInvalid\": \"限流窗口必须为 1 至 2147483647 的整数毫秒\",\n \"kit_rateLimitMemoryCapacity\": \"开发内存限流容量已满,请使用共享后端\",\n \"kit_transportBusy\": \"材料处理繁忙,请稍后重试\",\n \"kit_feedbackUnavailable\": \"问题反馈暂不可用,业务功能可继续使用\"\n}\n","/**\n * @h-ai/kit — i18n 工具\n *\n * Kit 模块的国际化消息工具,从 @h-ai/core 调用 i18n API。\n * @module kit-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 * Kit 中英日消息字典的合法 key 联合。\n * @example 模块内部上下文\n * ```ts\n * const key: KitMessageKey = 'kit_accessDenied'\n * const message = kitM(key)\n * void message\n * ```\n */\ntype KitMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取 Kit 模块的 i18n 消息\n * @example 模块内部上下文\n * ```ts\n * const message = kitM('kit_accessDenied', { locale: 'en-US' })\n * void message\n * ```\n */\nexport const kitM\n = core.i18n.createMessageGetter<KitMessageKey>({ 'zh-CN': messagesZhCN, 'en-US': messagesEnUS, 'ja-JP': messagesJaJP })\n\n/**\n * 设置浏览器应用语言或 Node 启动默认语言;请求中只更新当前 runWithLocale 上下文。\n *\n * 通过 @h-ai/core 的集中式 locale 管理器,一次调用即可同步所有模块。\n * 各模块的 createMessageGetter 优先读取当前 Node 请求上下文;SSR 必须包裹实际 resolve。\n * @param locale - 支持的应用默认语言;请求中应使用上下文隔离。\n * @example\n * ```ts\n * import { kit } from '@h-ai/kit'\n *\n * // 应用启动时设置默认语言;请求内使用 runWithLocale。\n * kit.i18n.setLocale('en-US')\n * ```\n */\nexport function setAllModulesLocale(locale: string): void {\n core.i18n.setGlobalLocale(locale)\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolveTransportPath, DEFAULT_TRANSPORT_KEY_EXCHANGE_PATH, shouldUseTransportForUrl } from './chunk-XC7I2L3G.js';
|
|
2
|
-
import { kitM } from './chunk-
|
|
2
|
+
import { kitM } from './chunk-KRR7QGMN.js';
|
|
3
3
|
import process from 'process';
|
|
4
4
|
import { ok, core, err } from '@h-ai/core';
|
|
5
|
+
import { IamSessionErrorCodeSchema, apiContract, IamReauthenticationProofSchema, IamPublicConfigSchema, IamCaptchaChallengeSchema } from '@h-ai/api-contract';
|
|
5
6
|
import { TRANSPORT_PROTOCOL } from '@h-ai/crypto';
|
|
6
|
-
import { apiContract, IamReauthenticationProofSchema, IamPublicConfigSchema, IamCaptchaChallengeSchema } from '@h-ai/api-contract';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
|
|
9
9
|
var DEFAULT_TOKEN_COOKIE_NAME = "hai_access_token";
|
|
@@ -211,11 +211,12 @@ function createKitClient(config = {}) {
|
|
|
211
211
|
fetch,
|
|
212
212
|
maxPlaintextBytes: transportConfig.maxPlaintextBytes
|
|
213
213
|
}) : null;
|
|
214
|
-
async function
|
|
214
|
+
async function performFetch(input, init = {}) {
|
|
215
215
|
const request = input instanceof Request ? input : void 0;
|
|
216
216
|
const method = (init.method ?? request?.method ?? "GET").toUpperCase();
|
|
217
217
|
const isWriteMethod = !["GET", "HEAD", "OPTIONS"].includes(method);
|
|
218
218
|
const headers = new Headers(init.headers ?? request?.headers);
|
|
219
|
+
headers.set("x-hai-terminal", config.terminalType ?? "web");
|
|
219
220
|
const suppliedId = headers.get("x-request-id");
|
|
220
221
|
const traceId = suppliedId && /^[\w.:-]{1,128}$/.test(suppliedId) ? suppliedId : core.id.uuid();
|
|
221
222
|
headers.set("x-request-id", traceId);
|
|
@@ -253,7 +254,36 @@ function createKitClient(config = {}) {
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
return {
|
|
256
|
-
apiFetch,
|
|
257
|
+
async apiFetch(input, init = {}, options) {
|
|
258
|
+
const enabled = config.recoverSession && options?.recoverSession !== false;
|
|
259
|
+
const original = enabled || options?.reauthorize ? new Request(input instanceof Request ? input : new URL(String(input), getCurrentOrigin()), init) : void 0;
|
|
260
|
+
const retry = original?.clone();
|
|
261
|
+
const response = await performFetch(original ?? input, original ? {} : init);
|
|
262
|
+
if (!retry || retry.signal.aborted || new URL(retry.url).origin !== getCurrentOrigin())
|
|
263
|
+
return response;
|
|
264
|
+
if (response.status !== 401 && !options?.reauthorize)
|
|
265
|
+
return response;
|
|
266
|
+
if (response.status !== 401 && !response.headers.get("content-type")?.includes("application/json"))
|
|
267
|
+
return response;
|
|
268
|
+
const failure = await response.clone().json().catch(() => void 0);
|
|
269
|
+
const code = failure && typeof failure === "object" && "success" in failure && failure.success === false && "error" in failure && failure.error && typeof failure.error === "object" && "code" in failure.error ? failure.error.code : void 0;
|
|
270
|
+
const proofRejected = code === "hai:iam:027" && options?.reauthorize;
|
|
271
|
+
if (!proofRejected && (!enabled || response.status !== 401 || typeof code === "string" && code.startsWith("hai:iam:") && !IamSessionErrorCodeSchema.safeParse(code).success))
|
|
272
|
+
return response;
|
|
273
|
+
if (!proofRejected && !await config.recoverSession())
|
|
274
|
+
return response;
|
|
275
|
+
if (retry.headers.has("x-hai-reauthentication") || options?.reauthorize) {
|
|
276
|
+
if (!options?.reauthorize)
|
|
277
|
+
return response;
|
|
278
|
+
const ticket = await options.reauthorize();
|
|
279
|
+
if (ticket === null || retry.signal.aborted)
|
|
280
|
+
return response;
|
|
281
|
+
retry.headers.delete("x-hai-reauthentication");
|
|
282
|
+
if (ticket)
|
|
283
|
+
retry.headers.set("x-hai-reauthentication", ticket);
|
|
284
|
+
}
|
|
285
|
+
return performFetch(retry);
|
|
286
|
+
},
|
|
257
287
|
get ready() {
|
|
258
288
|
return !transportClient || transportClient.ready();
|
|
259
289
|
},
|
|
@@ -273,7 +303,7 @@ function createKitAuthActions(options = {}) {
|
|
|
273
303
|
const apiFetch = options.apiFetch ?? createKitClient().apiFetch;
|
|
274
304
|
const base = options.basePath ?? "/api/auth";
|
|
275
305
|
async function post(path, input, init = {}) {
|
|
276
|
-
const response = await apiFetch(`${base}/${path}`, { ...init, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) });
|
|
306
|
+
const response = await apiFetch(`${base}/${path}`, { ...init, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) }, { recoverSession: false });
|
|
277
307
|
const result = apiContract.haiResultSchema(z.unknown().optional()).parse(await response.json());
|
|
278
308
|
return result.success ? ok(result.data) : result;
|
|
279
309
|
}
|
|
@@ -301,15 +331,15 @@ function createKitAuthActions(options = {}) {
|
|
|
301
331
|
return result.success ? ok(IamCaptchaChallengeSchema.parse(result.data)) : result;
|
|
302
332
|
},
|
|
303
333
|
async publicConfig() {
|
|
304
|
-
const response = await apiFetch(`${base}/config
|
|
334
|
+
const response = await apiFetch(`${base}/config`, void 0, { recoverSession: false });
|
|
305
335
|
return apiContract.haiResultSchema(IamPublicConfigSchema.nullable()).parse(await response.json());
|
|
306
336
|
},
|
|
307
337
|
login(input) {
|
|
308
338
|
if (input.method === "ldap")
|
|
309
|
-
return post("login/ldap", { username: input.identifier, password: input.password, captcha: input.captcha });
|
|
339
|
+
return post("login/ldap", { username: input.identifier, password: input.password, captcha: input.captcha, expectedUserId: input.expectedUserId });
|
|
310
340
|
if (input.method === "email" || input.method === "sms")
|
|
311
|
-
return post("login/otp", { identifier: input.identifier, code: input.code, captcha: input.captcha });
|
|
312
|
-
return post("login", { identifier: input.identifier, password: input.password, captcha: input.captcha });
|
|
341
|
+
return post("login/otp", { identifier: input.identifier, code: input.code, captcha: input.captcha, expectedUserId: input.expectedUserId });
|
|
342
|
+
return post("login", { identifier: input.identifier, password: input.password, captcha: input.captcha, expectedUserId: input.expectedUserId });
|
|
313
343
|
},
|
|
314
344
|
register: (input) => post("register", { ...input, confirmPassword: input.password }),
|
|
315
345
|
sendOtp: (identifier, options2) => post("otp/send", { identifier, ...options2 }),
|
|
@@ -324,5 +354,5 @@ function createKitAuthActions(options = {}) {
|
|
|
324
354
|
}
|
|
325
355
|
|
|
326
356
|
export { clearBrowserToken, configureAuth, createKitAuthActions, createKitClient, getAccessToken, installBrowserTransport, installBrowserTransportFetch, login, loginWithApiKey, loginWithLdap, loginWithOtp, logout, registerAndLogin, setBrowserToken };
|
|
327
|
-
//# sourceMappingURL=chunk-
|
|
328
|
-
//# sourceMappingURL=chunk-
|
|
357
|
+
//# sourceMappingURL=chunk-QPJ5AWXX.js.map
|
|
358
|
+
//# sourceMappingURL=chunk-QPJ5AWXX.js.map
|