@h-ai/feedback 0.1.0-alpha.72 → 0.1.0-alpha.74
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 +60 -3
- package/README.ja.md +60 -3
- package/README.md +60 -3
- package/dist/{chunk-PYKGS35A.js → chunk-UIVT267C.js} +5 -5
- package/dist/chunk-UIVT267C.js.map +1 -0
- package/dist/client/index.js +8 -3
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +76 -3
- package/dist/index.js +206 -20
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-PYKGS35A.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -12,7 +12,7 @@ Collect user feedback and diagnostic evidence, notify administrators, review rep
|
|
|
12
12
|
|
|
13
13
|
- Bounded browser/server logs, requests, errors, interactions, versions and allowed environment fields; up to 100 log records before and after an error by default.
|
|
14
14
|
- Transactional intake, stable submission/evidence identifiers, checksum validation, independent processing/collection/notification state and durable leased jobs.
|
|
15
|
-
- `notificationMode` independently selects `disabled` (no notification jobs), `console` (
|
|
15
|
+
- `notificationMode` independently selects `disabled` (no notification jobs), `console` (metadata only by default, including production, without Reach/email; consoleContent: summary explicitly enables sanitized summaries), or `reach` (any configured provider and destinations; only this mode requires recipients). The default remains `reach` for existing configurations; generated projects explicitly select `console`. During Reach DND, the feedback outbox waits without adding a second delayed queue or changing other notification policy. Production Reach Console restrictions remain enforced. Sent means channel acceptance, not inbox delivery.
|
|
16
16
|
- Browser screenshots, irreversible masking, annotations, optional rrweb recording, retries and local fallback export; `FeedbackModal`, `FeedbackList` and `FeedbackDetail` in `@h-ai/ui`.
|
|
17
17
|
- Self-contained ZIP files, SHA-256 validation, an offline viewer and paginated evidence queries without production credentials.
|
|
18
18
|
|
|
@@ -58,7 +58,7 @@ The complete client exposes `init(config)`, `prepare/captureScreenshot(attachmen
|
|
|
58
58
|
|
|
59
59
|
`addFiles` adds each batch atomically: all files must pass validation before merging. Any failure preserves existing attachments and missing markers without saving a partial batch. Successful concurrent batches preserve each other's files, with at most 20 merged attachments. If the draft changes or the first envelope freezes during reading, it returns `CONFLICT` without adding hidden uploads outside that envelope.
|
|
60
60
|
|
|
61
|
-
Local attachments support PNG/JPEG/WebP, WebM/MP4, TXT/LOG, JSON and
|
|
61
|
+
Local attachments support PNG/JPEG/WebP, WebM/MP4, TXT/LOG, JSON, PDF and ZIP/GZIP. Missing MIME types and `application/octet-stream` are inferred from supported extensions; common MIME aliases are normalized. The server still validates file bytes and checksums. Unsupported formats show a specific message. Screenshots preserve ordinary controls' backgrounds and borders while replacing their values with fixed mask characters. Passwords, `data-feedback-private`, `.rr-block` and `.rr-mask` remain fully covered. Only the capture clone changes; the original page and values remain intact.
|
|
62
62
|
|
|
63
63
|
`annotate(id, { ...rect, mode, coordinateSpace: 'ratio' })` accepts UI 0–1 coordinates directly. The SDK converts decoded intrinsic dimensions, including fractional/tiny selections, reverse dragging, edges and CSS scaling. Omitting coordinateSpace retains pixel coordinates. `getView()` owns and reuses preview URLs, releasing them on replacement, removal and close. Hosts need no image decoding or object URL bookkeeping.
|
|
64
64
|
|
|
@@ -66,7 +66,7 @@ Concurrent `init` returns `INIT_IN_PROGRESS`; `close` drains bounded screenshots
|
|
|
66
66
|
|
|
67
67
|
The SDK owns the draft and updates it through methods such as `annotate` and `addFiles`. Svelte hosts should keep this reference in `$state.raw<FeedbackDraft>()` and refresh attachment views from `getDraft()` after successful operations. A deep `$state` proxy can retain cached image bytes after the SDK replaces them on the original draft.
|
|
68
68
|
|
|
69
|
-
`maxTotalBytes` defaults to 32 MiB (4–40 MiB). `feedback.budget` derives individual/total attachment, duplicate evidence and Base64/encrypted transport budgets from it. Defaults are 5 MiB per file, 22 MiB for attachments, 4 MiB/2000 evidence records, plus 2 MiB for history, metadata and the viewer. Submission validates declared totals; evidence updates enforce quotas atomically; processing history is limited to 256 KiB. The authenticated `reports.policy({})` supplies effective limits to the client and form. The reference admin, API/fullstack templates and
|
|
69
|
+
`maxTotalBytes` defaults to 32 MiB (4–40 MiB). `feedback.budget` derives individual/total attachment, duplicate evidence and Base64/encrypted transport budgets from it. Defaults are 5 MiB per file, 22 MiB for attachments, 4 MiB/2000 evidence records, plus 2 MiB for history, metadata and the viewer. Submission validates declared totals; evidence updates enforce quotas atomically; processing history is limited to 256 KiB. The authenticated `reports.policy({})` supplies effective limits to the client and form. The reference admin, API/fullstack templates and deployment combine explicit host limits and feedback requirements with `resolveTransportBodyLimits(explicitLimit, { requiredPlaintextBytes: feedback.budget.maxTransportBytes })`, using the derived budget for transport, adapter-node and proxies. Insufficient explicit limits fail; only omitted/auto values derive capacity. Custom hosts combine limits during initialization; `hai add` preserves existing configuration.
|
|
70
70
|
|
|
71
71
|
`captureConsole` and `persistDraft` default to false. Persistence requires a host supplied, noncredential user/tenant `persistenceKey`. `keyScreenshots` defaults to true and follows the main `screenshot` switch; `maxKeyScreenshots` defaults to 3 with a hard limit of 5, and `keyScreenshotThrottleMs` defaults to 10000 milliseconds.
|
|
72
72
|
|
|
@@ -90,6 +90,57 @@ Console/disabled do not require the Reach package. Reach mode explicitly install
|
|
|
90
90
|
|
|
91
91
|
Web clients never resolve or install native plugins. Custom hosts import saveTauriFeedbackFile from @h-ai/feedback/tauri or saveCapacitorFeedbackFile from @h-ai/feedback/capacitor and call configureFeedbackSaving(saver) at startup. Generated hosts already configure this; business code does not handle native file lifecycles.
|
|
92
92
|
|
|
93
|
+
### Business evidence extension
|
|
94
|
+
|
|
95
|
+
Register a server callback with `feedback.init(config, { collectEvidence })`. A durable `collect` job invokes it after intake commits and awaits synchronous or asynchronous results without holding up submission. All instances handling the same appId must register the same callback so jobs can recover after restart or takeover.
|
|
96
|
+
|
|
97
|
+
This example runs after dependencies initialize. The host supplies `FeedbackConfigInput` and prepares a sanitized `diagnostics/sanitized-logs.zip`; the callback can instead await the application's own diagnostic service:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import type { FeedbackConfigInput } from '@h-ai/feedback'
|
|
101
|
+
import { readFile } from 'node:fs/promises'
|
|
102
|
+
import { feedback } from '@h-ai/feedback'
|
|
103
|
+
|
|
104
|
+
async function startFeedback(config: FeedbackConfigInput) {
|
|
105
|
+
const result = await feedback.init(config, {
|
|
106
|
+
collectEvidence: async ({ report, signal }) => ({
|
|
107
|
+
text: `Diagnostics for ${report.number}`,
|
|
108
|
+
data: { businessSystem: 'orders', scopeId: report.scopeId },
|
|
109
|
+
attachments: [{
|
|
110
|
+
id: 'business-logs',
|
|
111
|
+
name: 'business-logs.zip',
|
|
112
|
+
contentType: 'application/zip',
|
|
113
|
+
bytes: await readFile('diagnostics/sanitized-logs.zip', { signal }),
|
|
114
|
+
}],
|
|
115
|
+
}),
|
|
116
|
+
})
|
|
117
|
+
if (!result.success)
|
|
118
|
+
return result
|
|
119
|
+
return result
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Text and structured data become `source: server:business`, `kind: business` evidence, paginated in the Business evidence tab. Associated files are downloadable and included in offline exports. Existing sanitization, field truncation and evidence budgets apply; use `text/plain` attachments for complete logs with full-text redaction. ZIP/GZIP bytes are stored unchanged after signature, size and SHA-256 checks; archives are never unpacked, executed or internally redacted. The application must sanitize their contents before compression. Browser `addFiles` also accepts ZIP, GZ and TGZ.
|
|
124
|
+
|
|
125
|
+
`businessCollectionTimeoutMs` defaults to 30000ms (1–300000). Timeout and shutdown abort the supplied signal and stop waiting; pass it into asynchronous work. Throws, invalid files, capacity limits and storage failures retry the collect job up to `maxAttempts`, then expose `collectionStatus: failed`; administrators can call `retry({ id, target: 'collect' }, context)`. Callbacks may run again, so use report.id for external idempotency. Files are stored first; evidence and attachment metadata publish in one leased transaction, and a committed success marker prevents recollection. Stable attachment IDs contain 1–100 characters and receive a `server:business:` prefix. All files share the existing 20-item, per-file and total byte budgets.
|
|
126
|
+
|
|
127
|
+
### Webhook callback
|
|
128
|
+
|
|
129
|
+
A trusted `webhookUrl` creates a separate durable webhook job in the intake transaction. It waits for collection to reach complete/partial/failed, then sends `POST application/json` with `feedback.created`, including when collection failed. It is independent of Reach, notificationMode and DND.
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
businessCollectionTimeoutMs: 30000
|
|
133
|
+
webhookUrl: ${HAI_FEEDBACK_WEBHOOKURL:}
|
|
134
|
+
webhookSecret: ${HAI_FEEDBACK_WEBHOOKSECRET:}
|
|
135
|
+
webhookTimeoutMs: ${HAI_FEEDBACK_WEBHOOKTIMEOUTMS:10000}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
An empty URL disables delivery. Only HTTP(S) URLs without user credentials or fragments are accepted, and redirects are not followed. Configure destinations through trusted server deployment settings; internal URLs support internal integrations. Keep secrets in private server environment settings, never PUBLIC_ variables. The request timeout defaults to 10000ms (1–60000). Non-2xx responses, redirects, network errors and timeouts retry with exponential backoff from 1s to 60s, up to maxAttempts (default 5).
|
|
139
|
+
|
|
140
|
+
`FeedbackWebhookEvent` contains `event/eventId/createdAt/report/evidence/detailUrl?`. Evidence contains its revision, business collection success and attachment metadata, without file bytes, storage keys, configuration secrets or full logs. Read contents through authenticated APIs or an offline bundle. The first delivery freezes the payload; retries and restarts reuse its body and `Idempotency-Key: eventId`. Receivers should persist and deduplicate eventId before returning 2xx. Delivery is at least once: lost acknowledgements can cause duplicates. Changing the configured URL causes older jobs to fail rather than redirect to a different destination; drain them before switching.
|
|
141
|
+
|
|
142
|
+
Requests include `X-Hai-Feedback-Event` and `X-Hai-Feedback-Timestamp` (Unix seconds). With a secret, `X-Hai-Feedback-Signature: sha256=<hex>` is HMAC-SHA256 of `timestamp + '.' + raw request body`. Verify raw bytes, compare in constant time and enforce timestamp tolerance plus eventId deduplication. `get().jobs` exposes status, attempts and safe lastError codes. Admin Console and Fullstack details show Webhook status and administrator retry; programmatic retry uses `retry({ id, target: 'webhook' }, context)`. It is tracked separately from notificationStatus; success means the endpoint returned 2xx.
|
|
143
|
+
|
|
93
144
|
### Reviewing and exporting
|
|
94
145
|
|
|
95
146
|
`list/get/getEvidence` return scoped reports, details and paginated evidence. `setStatus({ id, status, expectedVersion }, context)` protects processing/reopening against concurrent overwrites. `retry` queues recoverable operations. `export` fixes an evidence revision; `getExport` downloads the completed ZIP. Missing/truncated evidence is explicit. Replay displays recorded state and does not reexecute business behavior.
|
|
@@ -115,6 +166,12 @@ The viewer binds only to loopback, uses a random URL and trusted built-in viewer
|
|
|
115
166
|
|
|
116
167
|
Check `success` for every domain result. `HaiFeedbackError` covers initialization, configuration, scope, missing records, conflicts, quota and checksum failures. HTTP 200 and SMTP acceptance do not prove final business success or inbox delivery; uncertain sends can be repeated.
|
|
117
168
|
|
|
169
|
+
### Privacy boundary of console notifications
|
|
170
|
+
|
|
171
|
+
`consoleContent` defaults to `metadata`: report number, application/version/build identity and collection status only. Descriptions, summaries, attachments, evidence, notification bodies and destinations are excluded. Explicit `summary` enables a generically sanitized summary; generic redaction cannot identify every business secret. Reach is not required and disabled/reach channel semantics are unchanged. Existing console configurations become metadata-only by default on upgrade.
|
|
172
|
+
|
|
173
|
+
Combine transport requirements with the host limit through `resolveTransportBodyLimits(configuredLimit, { requiredPlaintextBytes: feedback.budget.maxTransportBytes })`, instead of overwriting it. An insufficient explicit limit is a configuration conflict; omitted values or Serv/Kit auto derive capacity. Attachment and evidence limits remain independent.
|
|
174
|
+
|
|
118
175
|
## Development and testing
|
|
119
176
|
|
|
120
177
|
```sh
|
package/README.ja.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- ブラウザーとサーバーのログ、リクエスト、操作、バージョン、許可された環境情報を上限付きで収集します。既定ではエラーの前後それぞれ最大100件です。
|
|
14
14
|
- トランザクションによる受理、提出と証拠の冪等性、checksum 検証、処理・収集・通知状態の分離、永続ジョブとリース回復。
|
|
15
|
-
- `notificationMode` は独立して選択できます。`disabled` は通知ジョブなし、`console`
|
|
15
|
+
- `notificationMode` は独立して選択できます。`disabled` は通知ジョブなし、`console` は本番でも既定でメタデータだけを出力し Reach/メール不要(consoleContent: summary で一般的なマスキング済み概要を明示)、`reach` は任意の Provider と宛先を使い、このモードのみ recipients が必要です。既定の `reach` は既存設定を維持し、生成プロジェクトは `console` を明示します。Reach DND 中は feedback 自身の outbox で待機し、二重の遅延キューや他通知の方針変更を行いません。本番 Reach Console の制限は維持します。sent はチャネル受付であり受信完了を意味しません。
|
|
16
16
|
- スクリーンショット、復元不能なマスク、注釈、任意の rrweb 記録、再試行とローカル出力。UI は `FeedbackModal`、`FeedbackList`、`FeedbackDetail` を提供します。
|
|
17
17
|
- 自己完結 ZIP、SHA-256 検証、オフライン表示、証拠のページ検索。運用認証情報は含めません。
|
|
18
18
|
|
|
@@ -58,7 +58,7 @@ return submitted
|
|
|
58
58
|
|
|
59
59
|
`addFiles` は一括追加を原子的に行い、全ファイルの検証に成功してから統合します。一つでも失敗した場合、既存の添付と欠落マーカーを維持し、一部だけを保存しません。並行して成功した追加を上書きせず、統合後も最大 20 件です。読取中に下書きが変わるか初回送信内容が固定された場合は `CONFLICT` を返し、送信内容に含まれない添付を後から隠れてアップロードしません。
|
|
60
60
|
|
|
61
|
-
ローカル添付は PNG/JPEG/WebP、WebM/MP4、TXT/LOG、JSON、PDF に対応します。MIME が空または `application/octet-stream` の場合は対応する拡張子で補い、一般的な MIME 別名も正規化します。サーバーは実際のバイトとチェックサムを引き続き検証し、未対応形式には具体的な案内を表示します。通常の入力欄は背景と枠を保持し、値だけを固定マスク文字に置き換えます。パスワード、`data-feedback-private`、`.rr-block`、`.rr-mask` は全面を遮蔽します。撮影用クローンだけを変更し、元の画面と入力値は保持します。
|
|
61
|
+
ローカル添付は PNG/JPEG/WebP、WebM/MP4、TXT/LOG、JSON、PDF、ZIP/GZIP に対応します。MIME が空または `application/octet-stream` の場合は対応する拡張子で補い、一般的な MIME 別名も正規化します。サーバーは実際のバイトとチェックサムを引き続き検証し、未対応形式には具体的な案内を表示します。通常の入力欄は背景と枠を保持し、値だけを固定マスク文字に置き換えます。パスワード、`data-feedback-private`、`.rr-block`、`.rr-mask` は全面を遮蔽します。撮影用クローンだけを変更し、元の画面と入力値は保持します。
|
|
62
62
|
|
|
63
63
|
`annotate(id, { ...rect, mode, coordinateSpace: 'ratio' })` は UI の 0–1 座標を直接受け取り、SDK がデコード後の元画像サイズで換算します。小数、1% 未満、逆方向、端や CSS 拡大縮小に対応します。省略時は従来のピクセル座標です。`getView()` がプレビュー URL を管理し、置換・削除・終了時に解放するため、ホストの画像デコードや URL 管理は不要です。
|
|
64
64
|
|
|
@@ -66,7 +66,7 @@ return submitted
|
|
|
66
66
|
|
|
67
67
|
下書きは SDK が保持し、`annotate` や `addFiles` で更新します。Svelte 側では参照を `$state.raw<FeedbackDraft>()` に保持し、操作成功後に `getDraft()` から添付ビューを更新してください。深い `$state` プロキシに格納すると、SDK が元の下書きの画像バイトを置き換えても、キャッシュされた古いバイトが返る場合があります。
|
|
68
68
|
|
|
69
|
-
`maxTotalBytes` の既定値は 32 MiB(4–40 MiB)です。`feedback.budget` から単一・合計添付、二重保存する証拠と Base64/暗号化転送の上限を導出します。既定は各 5 MiB、添付合計 22 MiB、証拠 4 MiB/2000 件、履歴・メタデータ・ビューアー用 2 MiB です。受理前に添付宣言の合計を検証し、証拠更新は原子的に制限、処理履歴は 256 KiB までです。認証済み `reports.policy({})` でクライアントとフォームに有効値を渡します。管理画面、API/fullstack テンプレートとデプロイは `feedback.budget.maxTransportBytes`
|
|
69
|
+
`maxTotalBytes` の既定値は 32 MiB(4–40 MiB)です。`feedback.budget` から単一・合計添付、二重保存する証拠と Base64/暗号化転送の上限を導出します。既定は各 5 MiB、添付合計 22 MiB、証拠 4 MiB/2000 件、履歴・メタデータ・ビューアー用 2 MiB です。受理前に添付宣言の合計を検証し、証拠更新は原子的に制限、処理履歴は 256 KiB までです。認証済み `reports.policy({})` でクライアントとフォームに有効値を渡します。管理画面、API/fullstack テンプレートとデプロイは `resolveTransportBodyLimits(explicitLimit, { requiredPlaintextBytes: feedback.budget.maxTransportBytes })` で明示上限と必要量を合わせ、転送、adapter-node、プロキシに同じ導出値を適用します。明示上限不足はエラーとなり、省略/auto の場合のみ自動導出します。独自ホストでも初期化時に同じ処理を行ってください。`hai add` は既存設定を維持します。
|
|
70
70
|
|
|
71
71
|
`captureConsole` と `persistDraft` の既定値は false です。永続化にはホストが決定する非認証情報のユーザー/テナント `persistenceKey` が必要です。`keyScreenshots` は既定で true ですが `screenshot` 全体スイッチに従います。`maxKeyScreenshots` は既定 3、上限 5、`keyScreenshotThrottleMs` は既定 10000 ミリ秒です。
|
|
72
72
|
|
|
@@ -90,6 +90,57 @@ console/disabled は Reach パッケージ不要です。reach モードは opti
|
|
|
90
90
|
|
|
91
91
|
Web クライアントは原生プラグインを解決・導入しません。独自ホストは @h-ai/feedback/tauri の saveTauriFeedbackFile または @h-ai/feedback/capacitor の saveCapacitorFeedbackFile を読み込み、起動時に configureFeedbackSaving(saver) を呼びます。生成工程には接続済みで、業務コードはネイティブファイルの管理を行いません。
|
|
92
92
|
|
|
93
|
+
### 業務証拠の拡張
|
|
94
|
+
|
|
95
|
+
`feedback.init(config, { collectEvidence })` にサーバー側の収集関数を登録します。受付コミット後に永続 collect タスクが同期・非同期の戻り値を待機するため、送信リクエストは業務ログの生成を待ちません。同じ appId を処理する全インスタンスに同じ関数を登録し、再起動・引き継ぎ後も復旧できるようにします。
|
|
96
|
+
|
|
97
|
+
以下は依存モジュール初期化後の例です。config はホストが読み込んだ FeedbackConfigInput、diagnostics/sanitized-logs.zip は業務側で事前に生成・秘匿化したファイルです。実際には関数内で独自のログ収集処理を await できます。
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import type { FeedbackConfigInput } from '@h-ai/feedback'
|
|
101
|
+
import { readFile } from 'node:fs/promises'
|
|
102
|
+
import { feedback } from '@h-ai/feedback'
|
|
103
|
+
|
|
104
|
+
async function startFeedback(config: FeedbackConfigInput) {
|
|
105
|
+
const result = await feedback.init(config, {
|
|
106
|
+
collectEvidence: async ({ report, signal }) => ({
|
|
107
|
+
text: `Diagnostics for ${report.number}`,
|
|
108
|
+
data: { businessSystem: 'orders', scopeId: report.scopeId },
|
|
109
|
+
attachments: [{
|
|
110
|
+
id: 'business-logs',
|
|
111
|
+
name: 'business-logs.zip',
|
|
112
|
+
contentType: 'application/zip',
|
|
113
|
+
bytes: await readFile('diagnostics/sanitized-logs.zip', { signal }),
|
|
114
|
+
}],
|
|
115
|
+
}),
|
|
116
|
+
})
|
|
117
|
+
if (!result.success)
|
|
118
|
+
return result
|
|
119
|
+
return result
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
text/data は source: server:business、kind: business の証拠として詳細の「業務情報」タブにページ表示され、添付ファイルは認可付きダウンロードとオフライン出力に含まれます。短文・JSON は既存の秘匿化、切り詰め、証拠容量制限に従います。完全なログには全文を秘匿化する text/plain 添付を使用します。ZIP/GZIP は形式マーカー・サイズ・SHA-256 を確認してそのまま保存し、展開・実行・内部秘匿化は行いません。業務側で圧縮前に機密情報を除去してください。ブラウザーの addFiles も ZIP/GZ/TGZ に対応します。
|
|
124
|
+
|
|
125
|
+
businessCollectionTimeoutMs は既定 30000ms(1–300000)。タイムアウトと終了時に signal を中止して待機を終了するため、非同期処理にも signal を渡します。例外、不正ファイル、容量超過、保存失敗は maxAttempts まで再試行し、最終失敗は collectionStatus: failed になります。管理者は retry({ id, target: 'collect' }, context) で再実行できます。関数は再実行される可能性があるため report.id で外部処理を冪等化します。全ファイル保存後、証拠と索引をリース付きの同一トランザクションで公開し、成功マーカー保存後は再収集しません。添付 ID は安定した 1–100 文字とし、server:business: が自動付加されます。全添付で既存の 20 件・単一/合計容量制限を共有します。
|
|
126
|
+
|
|
127
|
+
### Webhook コールバック
|
|
128
|
+
|
|
129
|
+
信頼するサーバー設定 webhookUrl を指定すると、受付トランザクションで独立した永続 webhook タスクを作成します。収集が complete/partial/failed になった後、feedback.created を POST application/json で送信します。収集失敗時も送信し、Reach、notificationMode、通知抑止とは独立します。
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
businessCollectionTimeoutMs: 30000
|
|
133
|
+
webhookUrl: ${HAI_FEEDBACK_WEBHOOKURL:}
|
|
134
|
+
webhookSecret: ${HAI_FEEDBACK_WEBHOOKSECRET:}
|
|
135
|
+
webhookTimeoutMs: ${HAI_FEEDBACK_WEBHOOKTIMEOUTMS:10000}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
URL の空文字は無効化です。HTTP(S) のみ許可し、URL のユーザー資格情報・fragment は禁止、リダイレクトは追従しません。宛先は信頼できる配布設定で管理し、内部連携には内部 URL も使用できます。署名鍵はサーバーの非公開環境に保存し、PUBLIC_ 変数に含めません。webhookTimeoutMs は既定 10000ms(1–60000)。非 2xx、リダイレクト、通信障害、タイムアウトは 1s から最大 60s の指数バックオフで maxAttempts(既定 5)まで再試行します。
|
|
139
|
+
|
|
140
|
+
公開型 FeedbackWebhookEvent は event/eventId/createdAt/report/evidence/detailUrl? を含みます。evidence は修訂、業務証拠の収集成功、添付メタデータを持ち、本文・保存キー・設定秘密・全ログは送信しません。内容は認可付き API またはオフライン ZIP から取得します。初回送信前に本文を固定し、再試行・再起動でも本文と Idempotency-Key: eventId を維持します。受信側は eventId を永続化・重複排除した後に 2xx を返してください。配信は少なくとも 1 回で、応答消失による重複があります。URL 変更後の旧タスクは別の宛先に転送せず失敗するため、切替前に処理を完了します。
|
|
141
|
+
|
|
142
|
+
X-Hai-Feedback-Event と X-Hai-Feedback-Timestamp(Unix 秒)を送信します。鍵設定時は X-Hai-Feedback-Signature: sha256=<hex> を付加し、HMAC-SHA256(secret, timestamp + '.' + 元の本文) を計算します。受信側は元のバイトで検証し、定数時間比較、時刻許容幅、eventId による重複排除を実装します。get().jobs で状態、試行回数、安全な lastError コードを確認でき、管理台/Fullstack の詳細にも状態と再試行を表示します。管理者は retry({ id, target: 'webhook' }, context) を使用できます。notificationStatus とは別管理で、成功は受信先が 2xx を返したことを示します。
|
|
143
|
+
|
|
93
144
|
### 一覧、処理、出力
|
|
94
145
|
|
|
95
146
|
`list/get/getEvidence` は認可された報告・詳細・証拠ページを返します。`setStatus({ id, status, expectedVersion }, context)` で処理と取り消しの競合を防ぎます。`retry` は回復可能な操作のみ再登録します。`export` は証拠の版を固定し、`getExport` は完成済みZIPを返します。欠落や切り捨ては明示され、ページ再生は業務処理の再実行ではありません。
|
|
@@ -115,6 +166,12 @@ hai feedback view feedback-FB-1.zip
|
|
|
115
166
|
|
|
116
167
|
各業務結果の `success` を確認してください。`HaiFeedbackError` は初期化、設定、権限、存在しない記録、競合、容量、checksum エラーを表します。HTTP 200やSMTP受理だけでは、業務成功や受信箱への配信を証明できません。不確実な送信は重複する可能性があります。
|
|
117
168
|
|
|
169
|
+
### console 通知のプライバシー境界
|
|
170
|
+
|
|
171
|
+
`consoleContent` の既定は `metadata` で、報告番号、アプリ・バージョン・ビルド情報と収集状態だけを記録します。説明・概要・添付・証拠・通知本文・宛先は含みません。一般的なマスキング済み概要が必要な場合のみ `summary` を明示します。一般的なマスキングでは全業務機密を検出できません。Reach は不要で disabled/reach の意味は変わりません。更新後の既存 console 設定も既定でメタデータだけになります。
|
|
172
|
+
|
|
173
|
+
`resolveTransportBodyLimits(configuredLimit, { requiredPlaintextBytes: feedback.budget.maxTransportBytes })` でホスト上限と統合し、直接上書きしません。明示上限の不足は設定競合、省略または Serv/Kit の auto では必要量を計算します。添付と証拠の業務上限は独立です。
|
|
174
|
+
|
|
118
175
|
## 開発とテスト
|
|
119
176
|
|
|
120
177
|
```sh
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- 有界采集前后端日志、请求、错误、操作、版本与白名单环境信息;默认错误前后各最多 100 条。
|
|
14
14
|
- 数据库事务受理、提交/证据幂等、附件校验、独立的处理/采集/通知状态、持久化任务与租约恢复。
|
|
15
|
-
- 通知由 `notificationMode` 独立选择:`disabled` 不创建通知任务;`console`
|
|
15
|
+
- 通知由 `notificationMode` 独立选择:`disabled` 不创建通知任务;`console` 默认仅输出定位与采集状态元数据(consoleContent: metadata);显式 consoleContent: summary 才加入通用脱敏摘要,不依赖 Reach 或邮箱;`reach` 使用任意已配置 Provider 与接收方,仅此模式要求 recipients。默认 `reach` 保持旧配置兼容,新生成工程显式使用 `console`。Reach 免打扰期间由反馈自己的 outbox 等待,不进入 Reach 延迟队列,也不修改其他通知策略。Reach 的生产 Console Provider 限制保持不变;sent 仅表示选定通道受理,不能证明收信。
|
|
16
16
|
- 浏览器截图、遮挡/标注、可选 rrweb 回放、失败重试与本地导出;`@h-ai/ui` 提供 `FeedbackModal`、`FeedbackList` 和 `FeedbackDetail`。
|
|
17
17
|
- 自包含 ZIP、SHA-256 校验、离线查看与按请求/错误/时间分页读取,不包含生产凭据。
|
|
18
18
|
|
|
@@ -60,7 +60,7 @@ return submitted
|
|
|
60
60
|
|
|
61
61
|
`addFiles` 按批次原子添加:全部文件通过校验才合并,任一失败均保留原附件和缺失标记,不保存半批文件。并发成功批次不会相互覆盖,合并后仍最多 20 项;读取期间草稿切换或首次包络冻结时返回 `CONFLICT`,不会补入包络之外的隐藏上传。
|
|
62
62
|
|
|
63
|
-
本地附件支持 PNG/JPEG/WebP、WebM/MP4、TXT/LOG、JSON 和
|
|
63
|
+
本地附件支持 PNG/JPEG/WebP、WebM/MP4、TXT/LOG、JSON、PDF 和 ZIP/GZIP。浏览器未提供 MIME 或只提供 `application/octet-stream` 时按受支持扩展名补齐,常见 MIME 别名会规范化;服务端仍校验真实文件字节和 checksum。不支持的格式显示具体提示。普通输入控件的截图保留底色与边框,内容替换为固定遮罩字符;密码框、`data-feedback-private`、`.rr-block` 和 `.rr-mask` 继续整块遮挡。仅修改截图克隆,原页面和输入值不变。
|
|
64
64
|
|
|
65
65
|
`annotate(id, { ...rect, mode, coordinateSpace: 'ratio' })` 直接接受 UI 的 0–1 坐标,由 SDK 按解码后的原图换算,支持小数、小于 1% 的区域、反向拖动、边缘与 CSS 缩放。省略 coordinateSpace 仍使用原图像素。`getView()` 管理并复用预览 URL;替换、删除和关闭时释放。业务无需 Image.decode 或 URL.createObjectURL。
|
|
66
66
|
|
|
@@ -68,7 +68,7 @@ return submitted
|
|
|
68
68
|
|
|
69
69
|
草稿由 SDK 持有并通过 `annotate`、`addFiles` 等方法更新。Svelte 宿主保存该引用时使用 `$state.raw<FeedbackDraft>()`,操作成功后从 `getDraft()` 刷新附件视图;不要将 SDK 草稿放入深度 `$state` 代理,否则 SDK 替换图片字节后,代理可能继续返回已缓存的旧字节。
|
|
70
70
|
|
|
71
|
-
`maxTotalBytes` 默认 32 MiB(4–40 MiB)。`feedback.budget` 从同一总量推导单附件、附件总量、双份证据与 Base64/加密传输预算;默认单附件 5 MiB、附件合计 22 MiB、证据 4 MiB/2000 条,并预留 2 MiB 给历史、元数据和查看器。受理前检查附件声明总量,补采原子检查证据预算,处理历史限制 256 KiB,避免成功受理后因容量永久无法导出。客户端通过受权 `reports.policy({})` 取得有效值,表单展示容量;管理台、API/fullstack
|
|
71
|
+
`maxTotalBytes` 默认 32 MiB(4–40 MiB)。`feedback.budget` 从同一总量推导单附件、附件总量、双份证据与 Base64/加密传输预算;默认单附件 5 MiB、附件合计 22 MiB、证据 4 MiB/2000 条,并预留 2 MiB 给历史、元数据和查看器。受理前检查附件声明总量,补采原子检查证据预算,处理历史限制 256 KiB,避免成功受理后因容量永久无法导出。客户端通过受权 `reports.policy({})` 取得有效值,表单展示容量;管理台、API/fullstack 模板与框架部署通过 `resolveTransportBodyLimits(explicitLimit, { requiredPlaintextBytes: feedback.budget.maxTransportBytes })` 合并宿主上限与反馈需求,将同一派生值用于服务传输、adapter-node 和代理。显式值不足时报错,缺省/auto 才自动推导;已有自定义宿主需在初始化处同样合并;`hai add` 保留已有配置。
|
|
72
72
|
|
|
73
73
|
`captureConsole` 和 `persistDraft` 默认关闭;持久化启用时必须提供由宿主确定的非凭据用户/租户 `persistenceKey`。`keyScreenshots` 默认开启,仍受 `screenshot` 总开关控制;`maxKeyScreenshots` 默认 3、硬上限 5,`keyScreenshotThrottleMs` 默认 10000 毫秒。
|
|
74
74
|
|
|
@@ -92,6 +92,57 @@ Web 客户端不解析或安装原生插件。自定义 Tauri/Capacitor 宿主
|
|
|
92
92
|
|
|
93
93
|
console/disabled 无 Reach npm 依赖;选 reach 模式需显式安装 @h-ai/reach(optional peer)并由宿主初始化。凭据通知只返回生成的 receiptId,不保存或回传可能携带载荷的 Provider messageId。
|
|
94
94
|
|
|
95
|
+
### 业务证据扩展
|
|
96
|
+
|
|
97
|
+
业务系统通过 `feedback.init(config, { collectEvidence })` 注册一个服务端采集函数。它在反馈可靠入库后由持久 `collect` 任务调用,支持直接返回或 `async/await`;提交请求无需等待业务日志生成。相比进程内事件订阅,任务在重启或切换实例后仍可恢复。所有处理同一 appId 的实例必须注册相同采集函数。
|
|
98
|
+
|
|
99
|
+
下例运行在依赖已初始化的服务端;`config` 为宿主加载的 `FeedbackConfigInput`,业务预先生成并脱敏 `diagnostics/sanitized-logs.zip`,生产中可在回调内等待自己的日志收集服务:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import type { FeedbackConfigInput } from '@h-ai/feedback'
|
|
103
|
+
import { readFile } from 'node:fs/promises'
|
|
104
|
+
import { feedback } from '@h-ai/feedback'
|
|
105
|
+
|
|
106
|
+
async function startFeedback(config: FeedbackConfigInput) {
|
|
107
|
+
const result = await feedback.init(config, {
|
|
108
|
+
collectEvidence: async ({ report, signal }) => ({
|
|
109
|
+
text: `Diagnostics for ${report.number}`,
|
|
110
|
+
data: { businessSystem: 'orders', scopeId: report.scopeId },
|
|
111
|
+
attachments: [{
|
|
112
|
+
id: 'business-logs',
|
|
113
|
+
name: 'business-logs.zip',
|
|
114
|
+
contentType: 'application/zip',
|
|
115
|
+
bytes: await readFile('diagnostics/sanitized-logs.zip', { signal }),
|
|
116
|
+
}],
|
|
117
|
+
}),
|
|
118
|
+
})
|
|
119
|
+
if (!result.success)
|
|
120
|
+
return result
|
|
121
|
+
return result
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`text/data` 以 `source: server:business`、`kind: business` 写入证据,详情“业务信息”页签可分页查询;附件关联同一反馈,详情下载和离线导出均包含。短文本/JSON 遵循已有脱敏、字段截断和证据预算;完整日志使用 `text/plain` 附件,会全文脱敏。ZIP/GZIP 原样存储,仅校验类型标记、字节数与 SHA-256,不解压、不执行、不自动脱敏内部文件;业务须在压缩前清理凭据和个人敏感内容。浏览器 `addFiles` 同样支持 ZIP、GZ、TGZ。
|
|
126
|
+
|
|
127
|
+
回调最多等待 `businessCollectionTimeoutMs`(默认 30000ms,1–300000);超时或关闭触发 `signal`,框架停止等待,宿主必须将 signal 传给异步操作。抛错、超限、非法文件或存储失败均记录采集任务失败并按 `maxAttempts` 重试;耗尽后 `collectionStatus: failed`,管理员可 `retry({ id, target: 'collect' }, context)`。回调可能重新执行,使用 report.id 实现外部操作幂等。附件全部存储完成后,证据和附件索引在持有租约的同一事务发布;成功标记入库后不再调用回调。附件 ID 稳定且为 1–100 字符,框架自动加 `server:business:` 前缀;所有附件共享 20 项、单项/合计预算,不另开无限容量。
|
|
128
|
+
|
|
129
|
+
### Webhook 回调
|
|
130
|
+
|
|
131
|
+
配置可信服务端 `webhookUrl` 后,每次新反馈在受理事务中创建一个独立 `webhook` 任务。任务等待采集进入 complete/partial/failed,再发送 `POST application/json` 的 `feedback.created`;采集失败仍会回调。Webhook 不依赖 Reach,也不受 notificationMode 或 Reach 免打扰影响。
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
businessCollectionTimeoutMs: 30000
|
|
135
|
+
webhookUrl: ${HAI_FEEDBACK_WEBHOOKURL:}
|
|
136
|
+
webhookSecret: ${HAI_FEEDBACK_WEBHOOKSECRET:}
|
|
137
|
+
webhookTimeoutMs: ${HAI_FEEDBACK_WEBHOOKTIMEOUTMS:10000}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
地址默认空字符串(禁用),只允许 HTTP(S),禁止 URL 用户凭据/fragment,不跟随重定向。地址只能由可信部署配置提供;内网地址可用于内部系统回调。签名密钥仅放服务端 `.env.local` 或部署私有环境,不使用 `PUBLIC_`。`webhookTimeoutMs` 默认 10000ms(1–60000),非 2xx、重定向、网络错误及超时均进入指数退避重试(1s 起,最多 60s),最多 `maxAttempts`(默认 5)次。
|
|
141
|
+
|
|
142
|
+
公开载荷类型为 `FeedbackWebhookEvent`,包含 `event/eventId/createdAt/report/evidence/detailUrl?`。evidence 提供修订、业务材料是否成功和附件元数据;不发送附件正文、存储 key、配置密钥或全部日志。需要正文时使用现有受权 API 或离线包。首次发送前固定载荷,重试/重启保持相同正文和 `Idempotency-Key: eventId`。接收方应先持久化并按 eventId 去重,再返回 2xx;HTTP 副作用采用至少一次投递,响应丢失仍可能重发。修改回调地址后旧任务会失败,不会自动转投新地址;待旧任务处理完再切换。
|
|
143
|
+
|
|
144
|
+
每次请求发送 `X-Hai-Feedback-Event`、`X-Hai-Feedback-Timestamp`(Unix 秒);配置密钥时额外发送 `X-Hai-Feedback-Signature: sha256=<hex>`,计算方式为 `HMAC-SHA256(secret, timestamp + '.' + 原始请求正文)`。接收方用原始字节验签、恒定时间比较,并限制时间偏差及按 eventId 防重放。任务终态和尝试次数见 `get().jobs`,`lastError` 仅为安全错误码;管理台/Fullstack 详情展示 Webhook 状态,管理员可重试或调用 `retry({ id, target: 'webhook' }, context)`。它与 `notificationStatus` 分开记录,发送成功表示接收端返回 2xx。
|
|
145
|
+
|
|
95
146
|
### 查询、处理与导出
|
|
96
147
|
|
|
97
148
|
`list/get/getEvidence` 返回受权记录、详情与分页证据;`setStatus({ id, status, expectedVersion }, context)` 以版本检查执行处理/撤销。`retry` 仅重排可恢复项。`export` 固定证据修订并创建任务,通过 `getExport` 获取已生成的 ZIP;导出未完成不伪装成功。材料缺失和截断随包保留,回放不是重新执行业务。
|
|
@@ -117,6 +168,12 @@ hai feedback view feedback-FB-1.zip
|
|
|
117
168
|
|
|
118
169
|
领域错误使用 `HaiFeedbackError`,包括未初始化、配置、权限、找不到、冲突、容量及 checksum 失败。所有领域 API 先检查 `success`;HTTP 200 或 Promise 完成不代表反馈、上传或邮件成功。SMTP 受理不等于管理员收件箱已投递;不确定发送存在重发边界。
|
|
119
170
|
|
|
171
|
+
### console 通知的隐私边界
|
|
172
|
+
|
|
173
|
+
`consoleContent` 默认为 `metadata`:仅记录反馈编号、应用/版本/构建定位与采集状态,不输出说明、summary、附件、证据、通知正文或接收地址。需要通用脱敏摘要时显式设为 `summary`;通用脱敏不能识别所有业务秘密。该选项不需要 Reach,也不改变 disabled/reach 的通道语义。旧配置的 console 日志默认随本次升级收紧。
|
|
174
|
+
|
|
175
|
+
反馈传输需求通过 `resolveTransportBodyLimits(configuredLimit, { requiredPlaintextBytes: feedback.budget.maxTransportBytes })` 与宿主上限合并,不能直接覆盖。显式值不足时报配置冲突;缺省或 Serv/Kit 的 auto 才自动推导。附件和证据的业务预算不随传输容量扩大。
|
|
176
|
+
|
|
120
177
|
## 开发测试
|
|
121
178
|
|
|
122
179
|
```sh
|
|
@@ -27,7 +27,7 @@ var en_US_default = {
|
|
|
27
27
|
feedback_exportPending: "The feedback bundle is being generated. Please retry shortly",
|
|
28
28
|
feedback_limitExceeded: "Feedback evidence exceeds the allowed capacity",
|
|
29
29
|
feedback_invalidInput: "Invalid feedback input",
|
|
30
|
-
feedback_unsupportedAttachmentType: "Unsupported attachment format. Choose a PNG, JPEG, WebP, WebM, MP4, TXT, LOG, JSON or
|
|
30
|
+
feedback_unsupportedAttachmentType: "Unsupported attachment format. Choose a PNG, JPEG, WebP, WebM, MP4, TXT, LOG, JSON, PDF, ZIP or GZIP file",
|
|
31
31
|
feedback_checksumMismatch: "Attachment integrity verification failed",
|
|
32
32
|
feedback_received: "New feedback received",
|
|
33
33
|
feedback_submittedAt: "Submitted at (UTC)",
|
|
@@ -52,7 +52,7 @@ var ja_JP_default = {
|
|
|
52
52
|
feedback_exportPending: "\u554F\u984C\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u751F\u6210\u3057\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
53
53
|
feedback_limitExceeded: "\u8CC7\u6599\u304C\u8A31\u5BB9\u5BB9\u91CF\u3092\u8D85\u3048\u3066\u3044\u307E\u3059",
|
|
54
54
|
feedback_invalidInput: "\u5165\u529B\u304C\u7121\u52B9\u3067\u3059",
|
|
55
|
-
feedback_unsupportedAttachmentType: "\u672A\u5BFE\u5FDC\u306E\u6DFB\u4ED8\u5F62\u5F0F\u3067\u3059\u3002PNG\u3001JPEG\u3001WebP\u3001WebM\u3001MP4\u3001TXT\u3001LOG\u3001JSON\u3001PDF \u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
55
|
+
feedback_unsupportedAttachmentType: "\u672A\u5BFE\u5FDC\u306E\u6DFB\u4ED8\u5F62\u5F0F\u3067\u3059\u3002PNG\u3001JPEG\u3001WebP\u3001WebM\u3001MP4\u3001TXT\u3001LOG\u3001JSON\u3001PDF\u3001ZIP\u3001GZIP \u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
56
56
|
feedback_checksumMismatch: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u306E\u6574\u5408\u6027\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093",
|
|
57
57
|
feedback_received: "\u65B0\u3057\u3044\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F",
|
|
58
58
|
feedback_submittedAt: "\u5831\u544A\u65E5\u6642\uFF08UTC\uFF09",
|
|
@@ -77,7 +77,7 @@ var zh_CN_default = {
|
|
|
77
77
|
feedback_exportPending: "\u95EE\u9898\u5305\u6B63\u5728\u751F\u6210\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
78
78
|
feedback_limitExceeded: "\u53CD\u9988\u6750\u6599\u8D85\u8FC7\u5141\u8BB8\u7684\u5BB9\u91CF",
|
|
79
79
|
feedback_invalidInput: "\u53CD\u9988\u53C2\u6570\u65E0\u6548",
|
|
80
|
-
feedback_unsupportedAttachmentType: "\u4E0D\u652F\u6301\u6B64\u9644\u4EF6\u683C\u5F0F\uFF0C\u8BF7\u9009\u62E9 PNG\u3001JPEG\u3001WebP\u3001WebM\u3001MP4\u3001TXT\u3001LOG\u3001JSON \u6216
|
|
80
|
+
feedback_unsupportedAttachmentType: "\u4E0D\u652F\u6301\u6B64\u9644\u4EF6\u683C\u5F0F\uFF0C\u8BF7\u9009\u62E9 PNG\u3001JPEG\u3001WebP\u3001WebM\u3001MP4\u3001TXT\u3001LOG\u3001JSON\u3001PDF\u3001ZIP \u6216 GZIP \u6587\u4EF6",
|
|
81
81
|
feedback_checksumMismatch: "\u9644\u4EF6\u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25",
|
|
82
82
|
feedback_received: "\u6536\u5230\u65B0\u7684\u95EE\u9898\u53CD\u9988",
|
|
83
83
|
feedback_submittedAt: "\u53CD\u9988\u65F6\u95F4\uFF08UTC\uFF09",
|
|
@@ -97,5 +97,5 @@ function redactFeedbackText(text) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
export { HaiFeedbackError, feedbackM, redactFeedbackText };
|
|
100
|
-
//# sourceMappingURL=chunk-
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
100
|
+
//# sourceMappingURL=chunk-UIVT267C.js.map
|
|
101
|
+
//# sourceMappingURL=chunk-UIVT267C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feedback-types.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/feedback-i18n.ts","../src/feedback-sanitize-text.ts"],"names":["core"],"mappings":"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","file":"chunk-UIVT267C.js","sourcesContent":["import type {\n FeedbackAppendEvidenceInput,\n FeedbackAttachment,\n FeedbackAttachmentData,\n FeedbackBudget,\n FeedbackCompleteInput,\n FeedbackDetail,\n FeedbackEvidence,\n FeedbackEvidenceInput,\n FeedbackEvidenceQuery,\n FeedbackExportInput,\n FeedbackJob,\n FeedbackListInput,\n FeedbackReport,\n FeedbackRetryInput,\n FeedbackSetStatusInput,\n FeedbackSubmitInput,\n FeedbackUploadInput,\n} from '@h-ai/api-contract/feedback'\nimport type { ErrorInfo, HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { FeedbackConfig, FeedbackConfigInput } from './feedback-config.js'\nimport { core } from '@h-ai/core'\n\nconst FeedbackErrorInfo = {\n NOT_INITIALIZED: '010:503',\n INIT_IN_PROGRESS: '011:409',\n CONFIG_ERROR: '013:500',\n OPERATION_FAILED: '020:500',\n NOT_FOUND: '021:404',\n FORBIDDEN: '022:403',\n CONFLICT: '023:409',\n LIMIT_EXCEEDED: '024:413',\n INVALID_INPUT: '025:400',\n CHECKSUM_MISMATCH: '026:422',\n} satisfies ErrorInfo\n\n/**\n * Feedback 错误定义。\n * @example\n * ```ts\n * import { HaiFeedbackError } from '@h-ai/feedback'\n * const code = HaiFeedbackError.NOT_FOUND.code\n * void code\n * ```\n */\nexport const HaiFeedbackError = core.error.buildHaiErrorsDef('feedback', FeedbackErrorInfo)\n\n/**\n * 由可信服务端认证与授权策略生成的范围;HTTP 输入不能指定此对象。\n * @example\n * ```ts\n * import type { FeedbackContext } from '@h-ai/feedback'\n * const context: FeedbackContext = { scopeId: 'tenant-1', userId: 'user-1', permissions: ['feedback:create'] }\n * void context\n * ```\n */\nexport interface FeedbackContext {\n scopeId: string\n userId: string\n permissions?: readonly string[]\n}\n\n/**\n * 业务采集附件;ID 在同一反馈内稳定,正文由框架计算校验和并写入私有存储。\n * @example\n * ```ts\n * import type { FeedbackCollectedAttachment } from '@h-ai/feedback'\n * const file: FeedbackCollectedAttachment = { id: 'logs', name: 'logs.txt', contentType: 'text/plain', bytes: new TextEncoder().encode('diagnostics') }\n * void file\n * ```\n */\nexport interface FeedbackCollectedAttachment {\n id: string\n name: string\n contentType: string\n bytes: Uint8Array\n}\n\n/**\n * 业务补充材料;短文本/结构化信息出现在 business 证据,完整日志使用附件。\n * @example\n * ```ts\n * import type { FeedbackCollectedEvidence } from '@h-ai/feedback'\n * const evidence: FeedbackCollectedEvidence = { text: 'Order diagnostics', data: { orderId: 'order-1' } }\n * void evidence\n * ```\n */\nexport interface FeedbackCollectedEvidence {\n text?: string\n data?: FeedbackEvidenceInput['data']\n attachments?: FeedbackCollectedAttachment[]\n}\n\n/**\n * 采集扩展在持久任务中执行并等待异步结果;可能重试,宿主应按 report.id 幂等并响应 signal。\n * @example\n * ```ts\n * import type { FeedbackInitOptions } from '@h-ai/feedback'\n * const options: FeedbackInitOptions = { collectEvidence: async ({ report, signal }) => { signal.throwIfAborted(); return { text: report.number } } }\n * void options\n * ```\n */\nexport interface FeedbackInitOptions {\n collectEvidence?: (context: { report: Readonly<FeedbackReport>, signal: AbortSignal }) => FeedbackCollectedEvidence | Promise<FeedbackCollectedEvidence>\n}\n\n/**\n * 采集结束后发送的 feedback.created 事件;eventId 跨重试稳定,附件正文通过受权 API 读取。\n * @example\n * ```ts\n * import type { FeedbackWebhookEvent } from '@h-ai/feedback'\n * function eventKey(event: FeedbackWebhookEvent) { return event.eventId }\n * void eventKey\n * ```\n */\nexport interface FeedbackWebhookEvent {\n event: 'feedback.created'\n eventId: string\n createdAt: number\n report: FeedbackReport\n evidence: { revision: number, business: boolean, attachments: FeedbackAttachment[] }\n detailUrl?: string\n}\n\n/**\n * 反馈服务对象;领域操作返回 HaiResult,close 不释放宿主依赖连接。\n * @example\n * ```ts\n * import type { FeedbackContext, FeedbackFunctions } from '@h-ai/feedback'\n * async function submit(service: FeedbackFunctions, context: FeedbackContext) {\n * const result = await service.submit({ appId: 'orders', submissionId: 'stable-operation-id' }, context)\n * return result.success ? result.data.id : result.error.code\n * }\n * void submit\n * ```\n */\nexport interface FeedbackFunctions {\n init: (config: FeedbackConfigInput, options?: FeedbackInitOptions) => Promise<HaiResult<void>>\n close: () => Promise<HaiResult<void>>\n readonly isInitialized: boolean\n readonly config: FeedbackConfig | null\n readonly budget: FeedbackBudget\n submit: (input: FeedbackSubmitInput, context: FeedbackContext) => Promise<HaiResult<FeedbackReport>>\n list: (input: FeedbackListInput, context: FeedbackContext) => Promise<HaiResult<PaginatedResult<FeedbackReport>>>\n get: (id: string, context: FeedbackContext) => Promise<HaiResult<FeedbackDetail>>\n getEvidence: (input: FeedbackEvidenceQuery, context: FeedbackContext) => Promise<HaiResult<PaginatedResult<FeedbackEvidence>>>\n setStatus: (input: FeedbackSetStatusInput, context: FeedbackContext) => Promise<HaiResult<FeedbackReport>>\n appendEvidence: (input: FeedbackAppendEvidenceInput, context: FeedbackContext) => Promise<HaiResult<{ acknowledged: string[], evidenceRevision: number }>>\n completeCollection: (input: FeedbackCompleteInput, context: FeedbackContext) => Promise<HaiResult<FeedbackReport>>\n uploadAttachment: (input: FeedbackUploadInput, context: FeedbackContext) => Promise<HaiResult<FeedbackAttachment>>\n getAttachment: (input: { id: string, attachmentId: string }, context: FeedbackContext) => Promise<HaiResult<FeedbackAttachmentData>>\n retry: (input: FeedbackRetryInput, context: FeedbackContext) => Promise<HaiResult<void>>\n export: (input: FeedbackExportInput, context: FeedbackContext) => Promise<HaiResult<FeedbackJob>>\n getExport: (input: { id: string, jobId: string }, context: FeedbackContext) => Promise<HaiResult<FeedbackAttachmentData>>\n runJobs: () => Promise<HaiResult<{ processed: number }>>\n}\n\nexport type {\n FeedbackAppendEvidenceInput,\n FeedbackAttachment,\n FeedbackAttachmentData,\n FeedbackCompleteInput,\n FeedbackDetail,\n FeedbackEvidence,\n FeedbackEvidenceQuery,\n FeedbackExportInput,\n FeedbackJob,\n FeedbackListInput,\n FeedbackReport,\n FeedbackRetryInput,\n FeedbackSetStatusInput,\n FeedbackSubmitInput,\n FeedbackUploadInput,\n} from '@h-ai/api-contract/feedback'\n","{\n \"feedback_notInitialized\": \"Feedback has not been initialized\",\n \"feedback_initInProgress\": \"Feedback initialization is in progress\",\n \"feedback_configError\": \"Invalid feedback configuration or unavailable dependency\",\n \"feedback_operationFailed\": \"Feedback operation failed. Please retry\",\n \"feedback_notFound\": \"Feedback or evidence was not found\",\n \"feedback_forbidden\": \"Access to this feedback is denied\",\n \"feedback_conflict\": \"Feedback changed or an operation identifier conflicts. Refresh and retry\",\n \"feedback_exportPending\": \"The feedback bundle is being generated. Please retry shortly\",\n \"feedback_limitExceeded\": \"Feedback evidence exceeds the allowed capacity\",\n \"feedback_invalidInput\": \"Invalid feedback input\",\n \"feedback_unsupportedAttachmentType\": \"Unsupported attachment format. Choose a PNG, JPEG, WebP, WebM, MP4, TXT, LOG, JSON, PDF, ZIP or GZIP file\",\n \"feedback_checksumMismatch\": \"Attachment integrity verification failed\",\n \"feedback_received\": \"New feedback received\",\n \"feedback_submittedAt\": \"Submitted at (UTC)\",\n \"feedback_materialStatus\": \"Material status\",\n \"feedback_materialCollecting\": \"Collecting\",\n \"feedback_materialComplete\": \"Complete\",\n \"feedback_materialPartial\": \"Partially missing\",\n \"feedback_materialFailed\": \"Collection failed\",\n \"feedback_defaultSummary\": \"User feedback\",\n \"feedback_retryInputChanged\": \"The first submission has already been sent. Restore the original text and attachments to retry, then open a new feedback.\"\n}\n","{\n \"feedback_notInitialized\": \"フィードバックが初期化されていません\",\n \"feedback_initInProgress\": \"フィードバックを初期化しています\",\n \"feedback_configError\": \"設定が無効、または依存サービスが利用できません\",\n \"feedback_operationFailed\": \"操作に失敗しました。再試行してください\",\n \"feedback_notFound\": \"フィードバックまたは資料が見つかりません\",\n \"feedback_forbidden\": \"このフィードバックへのアクセス権限がありません\",\n \"feedback_conflict\": \"フィードバックが更新されたか操作識別子が重複しています。再読込してください\",\n \"feedback_exportPending\": \"問題パッケージを生成しています。しばらくしてから再試行してください\",\n \"feedback_limitExceeded\": \"資料が許容容量を超えています\",\n \"feedback_invalidInput\": \"入力が無効です\",\n \"feedback_unsupportedAttachmentType\": \"未対応の添付形式です。PNG、JPEG、WebP、WebM、MP4、TXT、LOG、JSON、PDF、ZIP、GZIP を選択してください\",\n \"feedback_checksumMismatch\": \"添付ファイルの整合性を確認できません\",\n \"feedback_received\": \"新しいフィードバックを受信しました\",\n \"feedback_submittedAt\": \"報告日時(UTC)\",\n \"feedback_materialStatus\": \"資料の状態\",\n \"feedback_materialCollecting\": \"収集中\",\n \"feedback_materialComplete\": \"完全\",\n \"feedback_materialPartial\": \"一部不足\",\n \"feedback_materialFailed\": \"収集失敗\",\n \"feedback_defaultSummary\": \"ユーザーのフィードバック\",\n \"feedback_retryInputChanged\": \"最初の送信は開始されています。元の説明と添付ファイルに戻して送信結果を確認してから、新しいフィードバックを作成してください。\"\n}\n","{\n \"feedback_notInitialized\": \"反馈模块尚未初始化\",\n \"feedback_initInProgress\": \"反馈模块正在初始化\",\n \"feedback_configError\": \"反馈配置无效或前置服务不可用\",\n \"feedback_operationFailed\": \"反馈操作失败,请重试\",\n \"feedback_notFound\": \"反馈或材料不存在\",\n \"feedback_forbidden\": \"没有访问该反馈的权限\",\n \"feedback_conflict\": \"反馈已更新或操作标识冲突,请刷新后重试\",\n \"feedback_exportPending\": \"问题包正在生成,请稍后重试\",\n \"feedback_limitExceeded\": \"反馈材料超过允许的容量\",\n \"feedback_invalidInput\": \"反馈参数无效\",\n \"feedback_unsupportedAttachmentType\": \"不支持此附件格式,请选择 PNG、JPEG、WebP、WebM、MP4、TXT、LOG、JSON、PDF、ZIP 或 GZIP 文件\",\n \"feedback_checksumMismatch\": \"附件完整性校验失败\",\n \"feedback_received\": \"收到新的问题反馈\",\n \"feedback_submittedAt\": \"反馈时间(UTC)\",\n \"feedback_materialStatus\": \"材料状态\",\n \"feedback_materialCollecting\": \"收集中\",\n \"feedback_materialComplete\": \"完整\",\n \"feedback_materialPartial\": \"部分缺失\",\n \"feedback_materialFailed\": \"采集失败\",\n \"feedback_defaultSummary\": \"用户提交的问题反馈\",\n \"feedback_retryInputChanged\": \"首次提交已发出,重试需保持原说明和附件。请恢复原输入完成确认后,再发起新反馈。\"\n}\n","import { core } from '@h-ai/core'\nimport en from '../messages/en-US.json'\nimport ja from '../messages/ja-JP.json'\nimport zh from '../messages/zh-CN.json'\n\n/**\n * 模块内消息获取器。\n * @example 模块内部上下文\n * ```ts\n * const message = feedbackM('feedback_notFound')\n * void message\n * ```\n */\nexport const feedbackM = core.i18n.createMessageGetter<keyof typeof zh>({ 'zh-CN': zh, 'en-US': en, 'ja-JP': ja })\n","import { core } from '@h-ai/core'\n\n/**\n * Node 与 Browser 共用的自由文本凭据脱敏;URL 与结构化字段仍复用 core。\n * @example 模块内部上下文\n * ```ts\n * const safeText = redactFeedbackText('{\"password\":\"example-only\"}')\n * void safeText\n * ```\n */\nexport function redactFeedbackText(text: string): string {\n return core.sanitize.sanitizeText(text)\n}\n"]}
|
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { feedbackM, HaiFeedbackError, redactFeedbackText } from '../chunk-
|
|
1
|
+
import { feedbackM, HaiFeedbackError, redactFeedbackText } from '../chunk-UIVT267C.js';
|
|
2
2
|
import { ok, core, err } from '@h-ai/core';
|
|
3
3
|
import { resolveFeedbackBudget, FeedbackSubmitInputSchema, FeedbackBudgetSchema, FeedbackEvidenceInputSchema, FeedbackReportSchema } from '@h-ai/api-contract/feedback';
|
|
4
4
|
|
|
@@ -326,7 +326,10 @@ function feedbackFileContentType(file) {
|
|
|
326
326
|
["txt", "text/plain"],
|
|
327
327
|
["log", "text/plain"],
|
|
328
328
|
["json", "application/json"],
|
|
329
|
-
["pdf", "application/pdf"]
|
|
329
|
+
["pdf", "application/pdf"],
|
|
330
|
+
["zip", "application/zip"],
|
|
331
|
+
["gz", "application/gzip"],
|
|
332
|
+
["tgz", "application/gzip"]
|
|
330
333
|
]);
|
|
331
334
|
if (!type || type === "application/octet-stream")
|
|
332
335
|
return extensions.get(file.name.split(".").at(-1)?.toLowerCase() ?? "");
|
|
@@ -336,7 +339,9 @@ function feedbackFileContentType(file) {
|
|
|
336
339
|
["image/pjpeg", "image/jpeg"],
|
|
337
340
|
["text/json", "application/json"],
|
|
338
341
|
["text/x-log", "text/plain"],
|
|
339
|
-
["application/x-pdf", "application/pdf"]
|
|
342
|
+
["application/x-pdf", "application/pdf"],
|
|
343
|
+
["application/x-zip-compressed", "application/zip"],
|
|
344
|
+
["application/x-gzip", "application/gzip"]
|
|
340
345
|
]);
|
|
341
346
|
const normalized = aliases.get(type) ?? type;
|
|
342
347
|
return [...extensions.values()].includes(normalized) ? normalized : void 0;
|