@h-ai/api-client 0.1.0-alpha9 → 1.0.0-beta.10
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 +212 -0
- package/README.ja.md +212 -0
- package/README.md +137 -265
- package/dist/index.d.ts +2770 -250
- package/dist/index.js +709 -633
- package/dist/index.js.map +1 -1
- package/package.json +19 -10
package/README.en.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @h-ai/api-client
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
Requests and token refresh use the current `core.i18n` locale as `Accept-Language`, so server errors follow the interface language. Set this header in `headers` to override it.
|
|
6
|
+
|
|
7
|
+
[Framework home](../../README.en.md) · Related modules: [@h-ai/api-contract](../api-contract/README.en.md) · [@h-ai/core](../core/README.en.md) · [@h-ai/crypto](../crypto/README.en.md)
|
|
8
|
+
|
|
9
|
+
## Introduction
|
|
10
|
+
|
|
11
|
+
A cross-platform oRPC/OpenAPI typed client for hai-framework, with consistent API calls and token management across Web, apps, and mini programs.
|
|
12
|
+
|
|
13
|
+
## Core features
|
|
14
|
+
|
|
15
|
+
- `apiClient`: the default typed client for the iam/storage/ai domains.
|
|
16
|
+
- `apiClient.create(contract)`: create a typed client for a custom contract.
|
|
17
|
+
- `apiClient.tokenStorage.{memory,httpOnlyCookie}()`: token storage factories. Browsers keep access tokens in memory and refresh tokens in server-managed httpOnly cookies; native clients inject platform secure storage. Credentials are never written to Web Storage.
|
|
18
|
+
- Custom `fetch` support for browsers, Node, Capacitor, and mini-program bridges.
|
|
19
|
+
- Automatic Bearer token injection, with token refresh and one retry after a 401.
|
|
20
|
+
- Network failures are converted to `HaiResult` errors.
|
|
21
|
+
- Optional transport encryption: `apiClient.init({ transport: { crypto } })` automatically uses `crypto.transport.createClient()`.
|
|
22
|
+
- Transport encryption and automatic refresh can run together: `/auth/refresh` after a 401 still uses encrypted fetch without falling back to plaintext.
|
|
23
|
+
|
|
24
|
+
## Getting started
|
|
25
|
+
|
|
26
|
+
### Quick start
|
|
27
|
+
|
|
28
|
+
`close()` invalidates retired refreshes and drains credential writes while preserving external TokenStorage values. After close completes, the same storage may be reused. `timeout` covers headers and the complete JSON body. The built-in Cookie adapter includes credentials for login and refresh, requiring explicit credentialed CORS origins; memory and native storage preserve the original request credentials. Custom application streams remain incremental and their bodies use the caller signal for cancellation.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { apiClient } from '@h-ai/api-client'
|
|
32
|
+
|
|
33
|
+
await apiClient.init({
|
|
34
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
35
|
+
auth: {}, // 默认使用 httpOnly cookie 存储(推荐);SSR 测试请显式传入 apiClient.tokenStorage.memory()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const login = await apiClient.iam.auth.login({
|
|
39
|
+
identifier: 'alice',
|
|
40
|
+
password: 'secret',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (login.success) {
|
|
44
|
+
// httpOnly cookie 模式下 refresh token 由服务端 Set-Cookie 管理,
|
|
45
|
+
// api-client 默认存储只需要把 access token 写入内存。
|
|
46
|
+
await apiClient.auth.setTokens(login.data.tokens)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const me = await apiClient.iam.auth.currentUser()
|
|
50
|
+
if (me.success) {
|
|
51
|
+
// 身份资料与服务端重新校验后的访问范围一次返回,客户端无需持久化权限快照。
|
|
52
|
+
const { roles, permissions, ...user } = me.data
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await apiClient.close()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Enable transport encryption
|
|
59
|
+
|
|
60
|
+
First enable `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` on the server. The client only needs the same `@h-ai/crypto` service instance; no manual key exchange code is required.
|
|
61
|
+
|
|
62
|
+
The server injects a managed serverKeyRing and shared registry. Clients receive only serverKeyId/public keys and include the negotiated version in encrypted requests; private keys never enter client configuration. After retirement of a key version, destroy the transport session and negotiate again; do not automatically replay non-idempotent operations.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { apiClient } from '@h-ai/api-client'
|
|
66
|
+
import { crypto } from '@h-ai/crypto'
|
|
67
|
+
|
|
68
|
+
await crypto.init()
|
|
69
|
+
|
|
70
|
+
await apiClient.init({
|
|
71
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
72
|
+
auth: {},
|
|
73
|
+
transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
await apiClient.close()
|
|
77
|
+
await crypto.close()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Only authenticated protocol v2 is accepted. Independent random SM4-CBC and HMAC-SM3 keys are wrapped with SM2; verify the sender signature and MAC before CBC decryption. Clients sign requests and the selected server key version signs responses. Authentication covers version, direction, serverKeyId, clientId, a random 128-bit requestId acting as the nonce, issuedAt, HTTP method, the SM3 pathHash of pathname plus the original search, and response status. Requests have a five-minute lifetime and at most 30 seconds of future clock skew. The shared registry atomically consumes (clientId, requestId), retaining it until at least issuedAt plus six minutes to cover node skew; storage failures reject requests. Synchronize node clocks and use a Redis noeviction/durable policy that preserves live nonces. Transport replay prevention does not replace business idempotency keys.
|
|
81
|
+
|
|
82
|
+
Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD carry it in X-Hai-Transport; bodyless responses such as 204/304/HEAD use the same header. Other requests/responses carry a JSON envelope and X-Encrypted: true. The client must match the original request context and HTTP status; missing markers, old formats and authentication failures reject. Serv/Kit hosts explicitly allow plaintext through `encryption: disabled` path rules or host `excludePaths`. Callers must select ordinary requests; API Client/Kit wrappers use their own `excludePaths`. CORS must allow and expose X-Hai-Transport. TLS authenticates handshake keys; this protocol does not replace IAM authorization.
|
|
83
|
+
|
|
84
|
+
### API contracts
|
|
85
|
+
|
|
86
|
+
The default `apiClient.ai.chats.createCompletion({ messages, tools, tool_choice })` uses a non-streaming JSON contract and retains complete nested function JSON Schema and `strict`. Check `success` before reading `data.choices[].message.tool_calls`. After authorization and execution, include the assistant tool calls and tool results with matching `tool_call_id` in the next `messages`. The input does not support `stream`; the server rejects it even if TypeScript is bypassed. Bind an application's own stream contract for streaming.
|
|
87
|
+
|
|
88
|
+
Custom applications can bind their own oRPC contract:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { apiClient } from '@h-ai/api-client'
|
|
92
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
93
|
+
|
|
94
|
+
const contract = apiContract.create({ iam: apiContract.iam })
|
|
95
|
+
const client = apiClient.create(contract)
|
|
96
|
+
|
|
97
|
+
await client.init({ baseUrl: 'https://api.example.com/api/v1' })
|
|
98
|
+
const result = await client.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API overview
|
|
102
|
+
|
|
103
|
+
- `apiClient.init(config)`: initialize the default client.
|
|
104
|
+
- `apiClient.close()`: clear client state.
|
|
105
|
+
- `apiClient.auth.setTokens(tokens)`: replace credentials and store the access token; non-httpOnly storage saves the supplied refresh token or clears its previous value when omitted.
|
|
106
|
+
- `apiClient.auth.clear()`: clear tokens.
|
|
107
|
+
- `apiClient.create(contract)`: create a custom typed client.
|
|
108
|
+
|
|
109
|
+
`auth.clear()` and `auth.setTokens()` immediately cancel and invalidate this client's previous refresh request. A late success or 401 cannot restore a logged-out session, overwrite a new account, or notify stale refresh callbacks. Storage writes are serialized, so an earlier asynchronous write cannot overwrite credentials after either method completes. Use these methods when logging out or switching accounts instead of writing to storage directly. The server logout endpoint must still revoke the httpOnly cookie and server session.
|
|
110
|
+
|
|
111
|
+
Calling `init()` on an initialized client returns `CONFIG_ERROR` and leaves the existing client intact. To change `baseUrl` / `fetch` / `transport`, call `await apiClient.close()` before initializing again.
|
|
112
|
+
|
|
113
|
+
### Configuration
|
|
114
|
+
|
|
115
|
+
- `baseUrl`: API base URL, usually including `/api/v1`.
|
|
116
|
+
- `auth.storage`: token storage adapter; defaults to `apiClient.tokenStorage.httpOnlyCookie()` (recommended for browsers). Explicitly use `apiClient.tokenStorage.memory()` for SSR / tests.
|
|
117
|
+
- `auth.refreshPath`: token refresh path; defaults to `/auth/refresh`.
|
|
118
|
+
- `timeout`: request timeout; defaults to 30000 ms.
|
|
119
|
+
- `headers`: static or dynamic shared request headers.
|
|
120
|
+
- `fetch`: custom fetch implementation.
|
|
121
|
+
- `transport.crypto`: transparent request/response encryption and decryption; requires an initialized `@h-ai/crypto` instance.
|
|
122
|
+
- `transport.keyExchangePath`: key exchange subpath; defaults to `/_hai/key-exchange` and is appended to `baseUrl` automatically.
|
|
123
|
+
- `apiClient.config`: a **redacted snapshot of runtime configuration**; credentials embedded in `baseUrl`, `headers.authorization`, and other sensitive values are hidden automatically.
|
|
124
|
+
|
|
125
|
+
### Configuration driven authentication
|
|
126
|
+
|
|
127
|
+
Password login submits only `identifier` and `password`; `AuthPanel` maps the `LoginForm` field `username` to `identifier`. Server settings `session.maxAge` / `session.refreshTokenMaxAge` control session lifetimes. The login form has no per-login persistent-session option.
|
|
128
|
+
|
|
129
|
+
Agreements support configuration-loaded userAgreementMarkdown/privacyPolicyMarkdown and URLs. Markdown takes priority for the same agreement and is safely rendered in a modal.
|
|
130
|
+
|
|
131
|
+
apiClient.createAuthActions(client) supplies publicConfig, login, register, sendOtp and password-reset actions for a client containing the standard IAM contract. Login uses identifier/password/method/code; successful authentication saves tokens through the existing configured storage. Applications only refresh their own state or navigate after authentication.
|
|
132
|
+
|
|
133
|
+
[完整 IAM 配置 / IAM configuration](../iam/README.en.md)
|
|
134
|
+
|
|
135
|
+
### Error handling
|
|
136
|
+
|
|
137
|
+
Business APIs return `HaiResult<T>`:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const result = await apiClient.iam.auth.currentUser()
|
|
141
|
+
if (!result.success) {
|
|
142
|
+
// result.error.code / result.error.message
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// result.data = User & { roles: string[], permissions: string[] }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Client-side failures such as missing initialization, network errors, timeouts, and 401/403/404 responses are also converted to failed `HaiResult` values.
|
|
150
|
+
|
|
151
|
+
A valid failed `HaiResult` returned with a non-2xx HTTP status retains its business error code, message and `traceId`; a 409 conflict can therefore explain how to retry. The actual HTTP response determines `httpStatus`. Internal 5xx diagnostics remain hidden even if the body claims status 400. Other response formats continue to use client error mappings.
|
|
152
|
+
|
|
153
|
+
Automatic refresh handles an actual HTTP 401 (`Response.status`) or a valid failed HaiResult over HTTP 200 (`success=false`, string `error.code/message` and `error.httpStatus=401`). After a successful refresh, the original request is retried once. Built-in login and registration routes and the configured `auth.refreshPath` never trigger automatic refresh; other failures such as 403/500 do not trigger it either. Ordinary serv procedures still return HTTP 200. Callers must check the final `success` and `error.code` and handle expired authentication when refresh fails.
|
|
154
|
+
|
|
155
|
+
## Input limits and HTTP capabilities
|
|
156
|
+
|
|
157
|
+
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#存储输入约束与跨端能力).
|
|
158
|
+
|
|
159
|
+
## Logical request observation
|
|
160
|
+
|
|
161
|
+
`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.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { apiClient } from '@h-ai/api-client'
|
|
165
|
+
|
|
166
|
+
const unsubscribe = apiClient.observeRequests((record) => {
|
|
167
|
+
const failed = !record.success
|
|
168
|
+
const requestId = record.requestId
|
|
169
|
+
})
|
|
170
|
+
unsubscribe()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Error messages and correlated logs
|
|
174
|
+
|
|
175
|
+
HTTP errors carry safe localized messages and a reference ID. ToastContainer hides the ID by default and reveals it through its diagnostic icon. The same ID remains 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.
|
|
176
|
+
|
|
177
|
+
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.
|
|
178
|
+
|
|
179
|
+
## Optional interactive challenges
|
|
180
|
+
|
|
181
|
+
createAuthActions(client) supplies createCaptcha({operation}) and preserves captcha in login mappings, renewal and registration. sendOtp(identifier,{captcha}) and requestPasswordReset(identifier,{captcha}) take an optional second argument. AuthPanel solves automatically; custom forms reuse ALTCHA without persisting proofs in TokenStorage.
|
|
182
|
+
|
|
183
|
+
See policy, defaults and recovery boundaries in [IAM](../iam/README.en.md).
|
|
184
|
+
|
|
185
|
+
## Sensitive request reauthentication
|
|
186
|
+
|
|
187
|
+
Pass the purpose-bound proof returned by `reauthenticate` through the individual call’s `options.context.reauthentication`; the request pipeline emits `X-Hai-Reauthentication`. Never place it in global client headers, persistent storage or logs. The server checks current permissions, resource scope and proof purpose before atomic consumption. Concurrent ordinary calls do not inherit the header. Cross-origin hosts must allow it; Serv/Kit defaults already include it.
|
|
188
|
+
|
|
189
|
+
## Recovering an expired session in place
|
|
190
|
+
|
|
191
|
+
Set `auth.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. Without the callback, behavior is unchanged; with it, recovery handles failure instead of immediate onRefreshFailed navigation. auth.terminalType (web/app/desktop/api; default web) selects a session group and does not attest hardware identity.
|
|
192
|
+
|
|
193
|
+
## Page exit session policy
|
|
194
|
+
|
|
195
|
+
`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.
|
|
196
|
+
|
|
197
|
+
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.
|
|
198
|
+
|
|
199
|
+
## Development and testing
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pnpm --filter @h-ai/api-client test
|
|
203
|
+
pnpm --filter @h-ai/api-client typecheck
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
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).
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
Apache-2.0
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @h-ai/api-client
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
通常のリクエストとトークン更新は、現在の `core.i18n` 言語を `Accept-Language` に設定します。サーバーのエラーも画面の言語に従います。`headers` で明示的に上書きできます。
|
|
6
|
+
|
|
7
|
+
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/api-contract](../api-contract/README.ja.md) · [@h-ai/core](../core/README.ja.md) · [@h-ai/crypto](../crypto/README.ja.md)
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
hai-framework のクロスプラットフォーム oRPC/OpenAPI 型付きクライアント。Web、アプリ、ミニプログラムに共通の API 呼び出しと Token 管理を提供します。
|
|
12
|
+
|
|
13
|
+
## 主な機能
|
|
14
|
+
|
|
15
|
+
- `apiClient`:iam/storage/ai ドメインに対応する既定の型付きクライアント。
|
|
16
|
+
- `apiClient.create(contract)`:独自の contract に対応する型付きクライアントを作成。
|
|
17
|
+
- `apiClient.tokenStorage.{memory,httpOnlyCookie}()`:Token ストレージのファクトリー。ブラウザーでは access token をメモリー、refresh token をサーバー管理の httpOnly Cookie に保持し、ネイティブではプラットフォームの安全なストレージを注入します。認証情報を Web Storage に保存しません。
|
|
18
|
+
- ブラウザー、Node、Capacitor、ミニプログラムのブリッジに対応する独自 `fetch` を指定可能。
|
|
19
|
+
- Bearer Token の自動付与と、401 応答後の更新および 1 回の再試行。
|
|
20
|
+
- ネットワークエラーを `HaiResult` のエラーに変換。
|
|
21
|
+
- 任意の通信暗号化:`apiClient.init({ transport: { crypto } })` が `crypto.transport.createClient()` を自動使用。
|
|
22
|
+
- 通信暗号化と自動更新を併用可能。401 後の `/auth/refresh` も暗号化された fetch を使用し、平文には切り替わりません。
|
|
23
|
+
|
|
24
|
+
## 使い始める
|
|
25
|
+
|
|
26
|
+
### クイックスタート
|
|
27
|
+
|
|
28
|
+
`close()` は旧更新を無効化し、進行中の認証情報書き込みを待ちます。外部 TokenStorage の値は保持され、終了後に再利用できます。`timeout` はヘッダーと JSON 本文全体が対象です。内蔵 Cookie アダプターはログインと更新で認証情報を送るため、CORS の Origin と credentials を設定します。メモリー・ネイティブ保存は元の要求の方針を維持します。独自ストリームは逐次読み取り、本文の中断には呼出側の signal を使います。
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { apiClient } from '@h-ai/api-client'
|
|
32
|
+
|
|
33
|
+
await apiClient.init({
|
|
34
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
35
|
+
auth: {}, // 默认使用 httpOnly cookie 存储(推荐);SSR 测试请显式传入 apiClient.tokenStorage.memory()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const login = await apiClient.iam.auth.login({
|
|
39
|
+
identifier: 'alice',
|
|
40
|
+
password: 'secret',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (login.success) {
|
|
44
|
+
// httpOnly cookie 模式下 refresh token 由服务端 Set-Cookie 管理,
|
|
45
|
+
// api-client 默认存储只需要把 access token 写入内存。
|
|
46
|
+
await apiClient.auth.setTokens(login.data.tokens)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const me = await apiClient.iam.auth.currentUser()
|
|
50
|
+
if (me.success) {
|
|
51
|
+
// 身份资料与服务端重新校验后的访问范围一次返回,客户端无需持久化权限快照。
|
|
52
|
+
const { roles, permissions, ...user } = me.data
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await apiClient.close()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### 通信暗号化の有効化
|
|
59
|
+
|
|
60
|
+
サーバーで先に `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })` を有効にします。クライアントには同じ `@h-ai/crypto` サービスのインスタンスを渡すだけで、鍵交換コードの実装は不要です。
|
|
61
|
+
|
|
62
|
+
サーバーには管理済み serverKeyRing と共有 registry を渡します。クライアントは serverKeyId と公開鍵だけを受け取り、暗号文リクエストに鍵の版を付けます。秘密鍵・鍵環をクライアント設定に含めません。版の失効後は transport セッションを破棄して再交渉し、非冪等処理を自動再送しません。
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { apiClient } from '@h-ai/api-client'
|
|
66
|
+
import { crypto } from '@h-ai/crypto'
|
|
67
|
+
|
|
68
|
+
await crypto.init()
|
|
69
|
+
|
|
70
|
+
await apiClient.init({
|
|
71
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
72
|
+
auth: {},
|
|
73
|
+
transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
await apiClient.close()
|
|
77
|
+
await crypto.close()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
認証済み v2 のみを受け付けます。独立したランダム SM4-CBC 鍵と HMAC-SM3 鍵を SM2 で包み、送信者署名、MAC の順に検証してから CBC を復号します。要求はクライアント秘密鍵、応答は選択されたサーバー鍵バージョンで署名します。version、direction、serverKeyId、clientId、nonce を兼ねるランダム 128 ビット requestId、issuedAt、HTTP method、pathname と元の search の SM3 pathHash、応答 status を認証します。要求の有効期限は 5 分、未来方向の時計差は最大 30 秒です。共有 registry が (clientId, requestId) を原子的に消費し、ノード間の時計差を含め issuedAt + 6 分まで保持します。保存障害では拒否します。時計同期と Redis の noeviction/永続化により有効な nonce を保持してください。業務の再試行には別の冪等キーが必要です。
|
|
81
|
+
|
|
82
|
+
空の POST も空平文の認証封装を送ります。GET/HEAD は X-Hai-Transport ヘッダー、204/304/HEAD など本文なし応答も同じヘッダーを使います。他の要求・応答は JSON 本文と X-Encrypted: true を使います。クライアントは元の要求コンテキストと HTTP status を照合し、マーカー欠落、旧形式、認証失敗では reject します。平文 endpoint は Serv/Kit の `encryption: disabled` ルールまたはホストの `excludePaths` で明示的に許可し、呼出側も通常の要求を選びます。API Client/Kit のラッパーは自身の `excludePaths` を使用します。CORS では X-Hai-Transport を許可・公開してください。鍵交換の公開鍵認証は TLS に依存し、IAM の業務認可は別に必要です。
|
|
83
|
+
|
|
84
|
+
### API 契約
|
|
85
|
+
|
|
86
|
+
既定の `apiClient.ai.chats.createCompletion({ messages, tools, tool_choice })` は非ストリーミング JSON 契約で、function のネストした JSON Schema と `strict` を保持します。`success` を確認してから `data.choices[].message.tool_calls` を読み、アプリで認可・実行した後、助手の呼び出しと対応する `tool_call_id` 付きツール結果を次の `messages` に含めます。`stream` は未対応で、TypeScript を回避してもサーバーが拒否します。ストリーミングにはアプリ独自の対応契約を利用します。
|
|
87
|
+
|
|
88
|
+
独自のアプリケーションでは、専用の oRPC contract を指定できます:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { apiClient } from '@h-ai/api-client'
|
|
92
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
93
|
+
|
|
94
|
+
const contract = apiContract.create({ iam: apiContract.iam })
|
|
95
|
+
const client = apiClient.create(contract)
|
|
96
|
+
|
|
97
|
+
await client.init({ baseUrl: 'https://api.example.com/api/v1' })
|
|
98
|
+
const result = await client.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API 一覧
|
|
102
|
+
|
|
103
|
+
- `apiClient.init(config)`:既定のクライアントを初期化。
|
|
104
|
+
- `apiClient.close()`:クライアントの状態を解放。
|
|
105
|
+
- `apiClient.auth.setTokens(tokens)`:認証情報を置き換えて access token を保存。httpOnly 以外では指定された refresh token も保存し、省略時は古い値を削除します。
|
|
106
|
+
- `apiClient.auth.clear()`:Token を削除。
|
|
107
|
+
- `apiClient.create(contract)`:独自の型付きクライアントを作成。
|
|
108
|
+
|
|
109
|
+
`auth.clear()` と `auth.setTokens()` は、このクライアントの既存の更新リクエストを直ちにキャンセルして無効化します。遅れて届いた成功応答や 401 は、ログアウト済みの認証状態を復元したり、新しいアカウントを上書きしたり、古い更新コールバックを通知したりしません。ストレージへの書き込みは直列化され、メソッド完了後に以前の非同期書き込みが認証情報を上書きすることはありません。ログアウトとアカウント切り替えには、ストレージを直接変更せずこれらのメソッドを使ってください。httpOnly Cookie とサーバーセッションは、引き続きサーバーのログアウトエンドポイントで失効させる必要があります。
|
|
110
|
+
|
|
111
|
+
初期化済みのクライアントで `init()` を再度呼ぶと `CONFIG_ERROR` が返り、既存のクライアントは維持されます。`baseUrl` / `fetch` / `transport` を変更する場合は、先に `await apiClient.close()` を呼んでから再初期化してください。
|
|
112
|
+
|
|
113
|
+
### 設定
|
|
114
|
+
|
|
115
|
+
- `baseUrl`:API のベース URL。通常は `/api/v1` を含みます。
|
|
116
|
+
- `auth.storage`:Token ストレージアダプター。既定は `apiClient.tokenStorage.httpOnlyCookie()`(ブラウザー推奨)。SSR / テストでは `apiClient.tokenStorage.memory()` を明示してください。
|
|
117
|
+
- `auth.refreshPath`:Token 更新パス。既定は `/auth/refresh`。
|
|
118
|
+
- `timeout`:リクエストのタイムアウト。既定は 30000 ms。
|
|
119
|
+
- `headers`:静的または動的な共通リクエストヘッダー。
|
|
120
|
+
- `fetch`:独自の fetch 実装。
|
|
121
|
+
- `transport.crypto`:透過的なリクエスト/レスポンス暗号化・復号。初期化済みの `@h-ai/crypto` インスタンスが必要です。
|
|
122
|
+
- `transport.keyExchangePath`:鍵交換のサブパス。既定は `/_hai/key-exchange` で、`baseUrl` に自動的に連結されます。
|
|
123
|
+
- `apiClient.config`:**機密情報をマスクした実行時設定のスナップショット**。`baseUrl` 内の認証情報や `headers.authorization` などの機密値は自動的に非表示になります。
|
|
124
|
+
|
|
125
|
+
### 設定による認証画面
|
|
126
|
+
|
|
127
|
+
パスワードログインの送信項目は `identifier` と `password` のみです。`AuthPanel` は `LoginForm` の `username` を `identifier` に変換します。有効期間はサーバーの `session.maxAge` / `session.refreshTokenMaxAge` で設定し、フォームにログインごとの永続セッション選択肢はありません。
|
|
128
|
+
|
|
129
|
+
規約は設定の userAgreementMarkdown/privacyPolicyMarkdown または URL を使用できます。同じ規約は Markdown 本文を優先して安全な Modal で描画します。
|
|
130
|
+
|
|
131
|
+
標準 IAM contract のクライアントには apiClient.createAuthActions(client) を使用します。複数方式、登録、OTP、復旧を統一し、認証成功時に設定済み TokenStorage に保存します。アプリ側は認証後の状態更新と遷移だけを担当します。
|
|
132
|
+
|
|
133
|
+
[完整 IAM 配置 / IAM configuration](../iam/README.ja.md)
|
|
134
|
+
|
|
135
|
+
### エラー処理
|
|
136
|
+
|
|
137
|
+
業務 API は `HaiResult<T>` を返します:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const result = await apiClient.iam.auth.currentUser()
|
|
141
|
+
if (!result.success) {
|
|
142
|
+
// result.error.code / result.error.message
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// result.data = User & { roles: string[], permissions: string[] }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
未初期化、ネットワークエラー、タイムアウト、401/403/404 などのクライアント側の問題も、失敗の `HaiResult` に変換されます。
|
|
150
|
+
|
|
151
|
+
非 2xx の HTTP 応答に有効な失敗 `HaiResult` が含まれる場合、業務エラーコード、メッセージ、`traceId` を保持します。409 の競合では再読み込みなどの対処方法を表示できます。`httpStatus` は実際の HTTP 応答を優先し、本文が 400 を指定しても 5xx の内部情報は公開しません。それ以外の応答形式はクライアント側のエラーに変換します。
|
|
152
|
+
|
|
153
|
+
自動更新は実際の HTTP 401(`Response.status`)、または HTTP 200 の有効な失敗 HaiResult(`success=false`、文字列の `error.code/message`、`error.httpStatus=401`)で動作します。更新が成功すると元のリクエストを一度だけ再試行します。組み込みのログイン・登録と設定された `auth.refreshPath` は自動更新の対象外で、403/500 など他の失敗でも更新しません。serv の通常の procedure は引き続き HTTP 200 を返します。呼び出し側は最終結果の `success` と `error.code` を確認し、更新失敗による認証失効を処理してください。
|
|
154
|
+
|
|
155
|
+
## 入力制約と HTTP 機能
|
|
156
|
+
|
|
157
|
+
キーは 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#存储输入约束与跨端能力)を参照してください。
|
|
158
|
+
|
|
159
|
+
## 論理リクエストの監視
|
|
160
|
+
|
|
161
|
+
`client.observeRequests` は論理呼び出しごとに一度通知し、解除関数を返します。requestId、procedure、所要時間、HTTP 状態、最終 success/errorCode、業務リクエストの attempts を含みます。HTTP 200 の失敗 HaiResult も失敗として扱い、認証更新後の再試行も同じ requestId を保持します。本文・Token・URL クエリーは含みません。認証更新や鍵交換をユーザー操作としません。close は購読を解除するため、再初期化後は再購読してください。
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { apiClient } from '@h-ai/api-client'
|
|
165
|
+
|
|
166
|
+
const unsubscribe = apiClient.observeRequests((record) => {
|
|
167
|
+
const failed = !record.success
|
|
168
|
+
const requestId = record.requestId
|
|
169
|
+
})
|
|
170
|
+
unsubscribe()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## エラー表示とログの関連付け
|
|
174
|
+
|
|
175
|
+
HTTP エラーには安全な翻訳文と追跡番号を含めます。ToastContainer は番号を既定で非表示にし、調査情報のアイコンから表示・コピーできます。同じ ID を error.traceId と X-Trace-Id に返します。requestId を traceId として共有し、ブラウザーとサーバーのログを関連付けます。脱機密化した例外・スタック・操作情報はログに残し、内部 cause/ext は公開しません。項目検証は翻訳済みの項目名とメッセージだけを返します。
|
|
176
|
+
|
|
177
|
+
HTTP/UI 境界には core.error.toPublic(error, traceId?)、再翻訳後の表示には core.error.formatMessage(error) を使い、ID を重複追加しません。ログには完全な Error を渡してください。DB ログにはドライバーコード、脱機密化したメッセージ・スタック、表・列を残し、SQL 本文、バインド値と接続認証情報を記録しません。
|
|
178
|
+
|
|
179
|
+
## 任意の対話チャレンジ
|
|
180
|
+
|
|
181
|
+
createAuthActions(client) は createCaptcha({operation}) を提供し、ログイン・更新・登録で captcha を保持します。sendOtp(identifier,{captcha}) と requestPasswordReset(identifier,{captcha}) は任意の第2引数に対応します。AuthPanel は自動求解し、独自フォームも ALTCHA 手順を使い、証明を TokenStorage に保存しません。
|
|
182
|
+
|
|
183
|
+
方針・既定値・復旧範囲は次を参照: [IAM](../iam/README.ja.md)。
|
|
184
|
+
|
|
185
|
+
## 機密操作の再認証
|
|
186
|
+
|
|
187
|
+
`reauthenticate` が返す用途限定証明を、個別呼び出しの `options.context.reauthentication` に渡すと `X-Hai-Reauthentication` が付きます。グローバルヘッダー、永続保存、ログには入れません。サーバーは現在の権限、対象範囲、用途を確認して原子的に消費し、並行する通常リクエストには引き継ぎません。クロスオリジン環境ではヘッダーを許可してください。Serv/Kit の既定設定には含まれます。
|
|
188
|
+
|
|
189
|
+
## 期限切れセッションの画面内復帰
|
|
190
|
+
|
|
191
|
+
`auth.recoverSession` によって現在の画面と入力を保持して再ログインできます。UI の `createSessionRecovery` / `SessionRecoveryDialog` を使い、ルートで公開方針と元の利用者を設定し、`recovery.recover` と標準認証 actions を渡して破棄時に cancel します。IAM は発行前に `expectedUserId` を確認し、通常の CAPTCHA・ロック・改密・アクセス規則を適用します。認証拒否が明確な要求だけ1回続行でき、取消・5xx・タイムアウト・結果不明の書込は再送しません。旧セッションの重要操作証明は再確認が必要な場合があります。コールバックなしは従来動作で、有効時は即時の onRefreshFailed 遷移を復帰処理が置き換えます。auth.terminalType(web/app/desktop/api、既定 web)は分類用で機器認証ではありません。
|
|
192
|
+
|
|
193
|
+
## ページ離脱時のセッション方針
|
|
194
|
+
|
|
195
|
+
`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` を併用してください。閉じた瞬間の失効は保証できません。
|
|
196
|
+
|
|
197
|
+
重要操作では `reauthorize: () => sensitiveActions.authorize(operation)` を Kit apiFetch の第3引数、または typed client の options.context に渡します。セッション更新・復帰または明確な証明拒否の後、新しい用途証明で1回だけ続行します。取消時は追加の書込を送らず、コールバックがなければ旧証明を再送しません。
|
|
198
|
+
|
|
199
|
+
## 開発とテスト
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pnpm --filter @h-ai/api-client test
|
|
203
|
+
pnpm --filter @h-ai/api-client typecheck
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`transport.maxPlaintextBytes` は鍵交換で取得します。ローカル上限を明示した場合は小さい値を採用します。容量エラーは `HaiApiClientError.PAYLOAD_TOO_LARGE`(`hai:api-client:020`)と方向・段階・バイト診断を返します。[容量と移行](../crypto/TRANSPORT-CAPACITY.md)を参照してください。
|
|
207
|
+
|
|
208
|
+
serv/kit は要求の結合、署名、MAC を検証した後、時間窓を超えた報文に暗号化した HTTP 400 時刻エラーを返します。クライアントは `TransportTimeError`(`hai:crypto:071`)で端末とサーバーの時刻同期・再読み込みを案内します。`api-client` は `VALIDATION_FAILED` に変換し `cause.transportCode` に原因を保持します。自動再送やリプレイ時間窓の緩和は行わず、改ざん・未認証の要求は従来の認証エラーです。
|
|
209
|
+
|
|
210
|
+
## ライセンス
|
|
211
|
+
|
|
212
|
+
Apache-2.0
|