@h-ai/feedback 0.1.0-alpha.69 → 0.1.0-alpha.71
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 +25 -8
- package/README.ja.md +25 -8
- package/README.md +25 -8
- package/dist/bundle/index.d.ts +2 -0
- package/dist/bundle/index.js +1 -1
- package/dist/capacitor.d.ts +18 -0
- package/dist/capacitor.js +35 -0
- package/dist/capacitor.js.map +1 -0
- package/dist/{chunk-DYEK2FNM.js → chunk-KGHZL7K6.js} +3 -6
- package/dist/chunk-KGHZL7K6.js.map +1 -0
- package/dist/{chunk-43AOXE4W.js → chunk-XQXX5WMV.js} +11 -6
- package/dist/chunk-XQXX5WMV.js.map +1 -0
- package/dist/client/index.d.ts +88 -6
- package/dist/client/index.js +325 -59
- package/dist/client/index.js.map +1 -1
- package/dist/feedback-client-files-B88nCOo_.d.ts +54 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +79 -34
- package/dist/index.js.map +1 -1
- package/dist/tauri.d.ts +18 -0
- package/dist/tauri.js +15 -0
- package/dist/tauri.js.map +1 -0
- package/package.json +42 -6
- package/dist/chunk-43AOXE4W.js.map +0 -1
- package/dist/chunk-DYEK2FNM.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
|
-
-
|
|
15
|
+
- `notificationMode` independently selects `disabled` (no notification jobs), `console` (sanitized summaries, including production, without Reach/email), 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
|
|
|
@@ -20,7 +20,7 @@ Collect user feedback and diagnostic evidence, notify administrators, review rep
|
|
|
20
20
|
|
|
21
21
|
Supply `appVersion/buildId/commit` from each actual browser or server artifact. A browser must retain its own identity; one SSR build shares its buildId across client and server output. Admin Console and generated base/API/fullstack projects inline these values. `hai build` creates internal metadata and passes it through Docker build arguments. Each target reads its own package.json version; a missing owning Git commit is `unknown`. Template `build-metadata.json` contains only these three nonsecret fields and matches the inlined values.
|
|
22
22
|
|
|
23
|
-
Initialize core and
|
|
23
|
+
Initialize core, reldb and private storage before feedback; initialize Reach only for reach notifications. Production uses shared databases and private object storage. The host owns connection credentials.
|
|
24
24
|
|
|
25
25
|
The following snippet runs inside an async function after dependencies are initialized. Trusted server authentication and authorization must supply the context:
|
|
26
26
|
|
|
@@ -30,8 +30,7 @@ import { feedback } from '@h-ai/feedback'
|
|
|
30
30
|
const initialized = await feedback.init({
|
|
31
31
|
appId: 'orders',
|
|
32
32
|
appVersion: '1.0.0',
|
|
33
|
-
|
|
34
|
-
notificationProvider: 'email',
|
|
33
|
+
notificationMode: 'console',
|
|
35
34
|
adminUrl: 'https://app.example.test/admin/feedback',
|
|
36
35
|
})
|
|
37
36
|
if (!initialized.success)
|
|
@@ -55,22 +54,40 @@ Browser and server text redaction share rules for common credential keys, JSON/e
|
|
|
55
54
|
|
|
56
55
|
`text/plain` attachments are redacted in full within the 5 MiB file limit, preserving safe content and trailing log entries without structured evidence's per-field truncation. Oversized original files are rejected before reading, and redacted output remains subject to the same attachment byte limit. JSON attachments and structured evidence retain field length, depth and count limits and may be trimmed; use plain text for complete logs. The same rules apply to pending attachments and files added to a local export after the first submission envelope is frozen.
|
|
57
56
|
|
|
58
|
-
The complete client exposes `init(config)`, `prepare/submit/flush/record/getDraft/annotate/addFiles/exportDraft
|
|
57
|
+
The complete client exposes `init(config)`, `prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/close`. Connect form `onfileschange` to `addFiles` and refresh draft attachment previews so new images can be marked, masked or removed before submission. Use `data-feedback-action` for key interactions, `data-feedback-private` for sensitive content and `data-feedback-ignore` for the feedback UI itself. The first transmitted envelope is frozen; changing input during a retry returns a conflict and preserves the input for local export.
|
|
59
58
|
|
|
60
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.
|
|
61
60
|
|
|
62
|
-
`annotate(id,
|
|
61
|
+
`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.
|
|
63
62
|
|
|
64
63
|
Concurrent `init` returns `INIT_IN_PROGRESS`; `close` drains bounded screenshots before clearing the session. Closing during capture makes `prepare` fail without resurrecting a draft. An annotation that finishes after submission, close, image removal or another annotation returns `CONFLICT`, preserving submitted bytes and checksums.
|
|
65
64
|
|
|
66
65
|
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.
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
`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 framework deployment use `feedback.budget.maxTransportBytes` for transport, adapter-node and proxies. Existing custom hosts wire this value during initialization; `hai add` preserves existing configuration.
|
|
69
68
|
|
|
70
69
|
`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.
|
|
71
70
|
|
|
72
71
|
Automatic screenshots reconstruct the visible page from a redacted DOM clone through SVG foreignObject. Cross-origin images, iframe/video content and network fonts that are not ready may be missing. Browser or CSP failures are recorded as missing, and users can still attach images manually.
|
|
73
72
|
|
|
73
|
+
### Feedback capture and delivery checks
|
|
74
|
+
|
|
75
|
+
The current screenshot is shown first. `captureScreenshot(attachmentId?)` adds the visible page or replaces that attachment. Connect `FeedbackModal.oncapture` to it: the modal hides while the user navigates and chooses “Capture this page”, retaining form input. `FeedbackForm.evidence` exposes sanitized logs, requests, environment facts and replay events; use `evidenceSummary` for missing-material notes. Browser facts come from the actual URL (without query/hash), viewport, language and compiled build. Server environment variables include only non-credential `envAllowlist` keys; absent values are null.
|
|
76
|
+
|
|
77
|
+
Supported browsers offer “Record screen → Start recording”: users select a tab/window and record silent video for up to 60 seconds / 5 MiB at a low frame rate and bitrate. Stopping sharing, cancellation, timeout and component destruction release the stream. Video contains the selected raw screen without automatic masking; users can preview or remove it before sending. WebM/MP4 use the same private storage, checksum and 5 MiB attachment limit. Browsers without capture support can attach video files.
|
|
78
|
+
|
|
79
|
+
The reference admin console and feedback-enabled fullstack templates explicitly enable `replay: true`; the SDK default remains false. Administrators use the detail “Page replay” tab to play or seek captured interactions. The host `onreplay` must load every page of `kind: replay` events. An empty recording has an explicit message; videos play under activity and attachments. After saving feedback locally, `hai feedback view <zip>` opens screenshots, video and replay. Local bundles mark server evidence that has not been obtained.
|
|
80
|
+
|
|
81
|
+
`saveDraft(input?)` exports and saves through Web download, the Tauri save dialog or Capacitor cache/native sharing. Cancellation returns `status: 'cancelled'`; failures remain failures. Native plugins are optional peers and are configured by generated App/Desktop projects. Custom hosts install the corresponding plugins. `createFeedbackReview(typedClient.feedback.reports)` provides `loadReplay(id)` and cancellable `exportReport(id)`. On logout call `review.close()` and `client.close({ discard: true })` to stop collection/polling and clear previews and persisted drafts.
|
|
82
|
+
|
|
83
|
+
Build identities share a 128-character limit. Invalid explicit HAI_BUILD_ID/HAI_BUILD_COMMIT fail the build without echoing values. Default redaction covers verificationCode, otpCode and common verification fields without hiding every business code.
|
|
84
|
+
|
|
85
|
+
When server policy is unavailable, bounded local capture/export remain available with server-policy-unavailable. Online submission revalidates policy first. addFiles(files, removedAttachmentIds?) atomically removes deleted files while staging new ones so displayed and effective capacity agree.
|
|
86
|
+
|
|
87
|
+
Console/disabled do not require the Reach package. Reach mode explicitly installs and initializes the @h-ai/reach optional peer. Credential sends expose generated receiptId only; provider messageId is neither persisted nor returned.
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
74
91
|
### Reviewing and exporting
|
|
75
92
|
|
|
76
93
|
`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.
|
|
@@ -92,7 +109,7 @@ The viewer binds only to loopback, uses a random URL and trusted built-in viewer
|
|
|
92
109
|
|
|
93
110
|
### Configuration and errors
|
|
94
111
|
|
|
95
|
-
`FeedbackConfigSchema` validates appId, recipients, notificationProvider, adminUrl, collectionTimeoutMs, workerIntervalMs, leaseMs, maxAttempts, retentionDays, envAllowlist and attachment/evidence quotas. Explicit YAML environment mappings use `HAI_FEEDBACK_*`. Browser policy must not expose SMTP or S3 secrets.
|
|
112
|
+
`FeedbackConfigSchema` validates appId, notificationMode, recipients, maxTotalBytes, notificationProvider, adminUrl, collectionTimeoutMs, workerIntervalMs, leaseMs, maxAttempts, retentionDays, envAllowlist and attachment/evidence quotas. Explicit YAML environment mappings use `HAI_FEEDBACK_*`. Browser policy must not expose SMTP or S3 secrets.
|
|
96
113
|
|
|
97
114
|
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.
|
|
98
115
|
|
package/README.ja.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- ブラウザーとサーバーのログ、リクエスト、操作、バージョン、許可された環境情報を上限付きで収集します。既定ではエラーの前後それぞれ最大100件です。
|
|
14
14
|
- トランザクションによる受理、提出と証拠の冪等性、checksum 検証、処理・収集・通知状態の分離、永続ジョブとリース回復。
|
|
15
|
-
-
|
|
15
|
+
- `notificationMode` は独立して選択できます。`disabled` は通知ジョブなし、`console` は本番でも脱機密化した概要のみを出力し Reach/メール不要、`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
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
ブラウザーとサーバーには各実成果物の `appVersion/buildId/commit` を渡します。フロント側は自分の識別情報を保持し、同じ SSR ビルドの client/server 出力は buildId を共有します。管理画面と base/API/fullstack テンプレートにはコンパイル時の識別情報を組み込んでいます。`hai build` は内部で識別情報を生成し、Docker のビルド引数にも渡します。バージョンは各対象の package.json から取得し、所属する Git コミットがない場合は `unknown` を記録します。テンプレートの `build-metadata.json` には同じ三つの非機密項目のみを保存します。
|
|
22
22
|
|
|
23
|
-
core、reldb
|
|
23
|
+
core、reldb、非公開 storage の後に feedback を初期化します。Reach は reach 通知モードのみ必要です。本番は共有 DB と非公開ストレージを使用し、接続認証情報はホストが管理します。
|
|
24
24
|
|
|
25
25
|
依存モジュール初期化済みの非同期関数内の例です。context は信頼できるサーバー認証・認可から取得します。
|
|
26
26
|
|
|
@@ -30,8 +30,7 @@ import { feedback } from '@h-ai/feedback'
|
|
|
30
30
|
const initialized = await feedback.init({
|
|
31
31
|
appId: 'orders',
|
|
32
32
|
appVersion: '1.0.0',
|
|
33
|
-
|
|
34
|
-
notificationProvider: 'email',
|
|
33
|
+
notificationMode: 'console',
|
|
35
34
|
adminUrl: 'https://app.example.test/admin/feedback',
|
|
36
35
|
})
|
|
37
36
|
if (!initialized.success)
|
|
@@ -55,22 +54,40 @@ return submitted
|
|
|
55
54
|
|
|
56
55
|
`text/plain` 添付は 5 MiB のファイル上限内で全文を秘匿処理し、安全な本文と末尾のログを保持します。構造化証拠のフィールド切り捨ては適用しません。上限を超える元ファイルは読取前に拒否し、秘匿処理後も同じ添付バイト上限を適用します。JSON 添付と構造化証拠には文字列長・深さ・件数の上限が残り、切り捨てられる場合があります。完全なログにはプレーンテキスト添付を使用してください。送信前の添付と、初回送信内容の固定後にローカル出力へ追加したファイルに同じ規則を適用します。
|
|
57
56
|
|
|
58
|
-
クライアントの全操作は `init(config)`、`prepare/submit/flush/record/getDraft/annotate/addFiles/exportDraft
|
|
57
|
+
クライアントの全操作は `init(config)`、`prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/close` です。フォームの `onfileschange` を `addFiles` に接続し、下書きの添付プレビューを更新すると、追加画像を送信前に注釈・遮蔽・削除できます。重要操作は `data-feedback-action`、機密部分は `data-feedback-private`、フィードバック画面自体の除外は `data-feedback-ignore` で指定します。初回送信後の内容は固定され、再試行中の入力変更は競合として入力を保持し、ローカル保存できます。
|
|
59
58
|
|
|
60
59
|
`addFiles` は一括追加を原子的に行い、全ファイルの検証に成功してから統合します。一つでも失敗した場合、既存の添付と欠落マーカーを維持し、一部だけを保存しません。並行して成功した追加を上書きせず、統合後も最大 20 件です。読取中に下書きが変わるか初回送信内容が固定された場合は `CONFLICT` を返し、送信内容に含まれない添付を後から隠れてアップロードしません。
|
|
61
60
|
|
|
62
|
-
`annotate(id,
|
|
61
|
+
`annotate(id, { ...rect, mode, coordinateSpace: 'ratio' })` は UI の 0–1 座標を直接受け取り、SDK がデコード後の元画像サイズで換算します。小数、1% 未満、逆方向、端や CSS 拡大縮小に対応します。省略時は従来のピクセル座標です。`getView()` がプレビュー URL を管理し、置換・削除・終了時に解放するため、ホストの画像デコードや URL 管理は不要です。
|
|
63
62
|
|
|
64
63
|
並行 `init` は `INIT_IN_PROGRESS` を返します。`close` は時間制限付き画面取得の終了後にセッションを解放します。取得中の終了は `prepare` を失敗させ、草稿を復活させません。送信、終了、画像削除、別の注釈更新後に完了した注釈は `CONFLICT` を返し、送信済みバイト列と checksum を変更しません。
|
|
65
64
|
|
|
66
65
|
下書きは SDK が保持し、`annotate` や `addFiles` で更新します。Svelte 側では参照を `$state.raw<FeedbackDraft>()` に保持し、操作成功後に `getDraft()` から添付ビューを更新してください。深い `$state` プロキシに格納すると、SDK が元の下書きの画像バイトを置き換えても、キャッシュされた古いバイトが返る場合があります。
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
`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` を転送、adapter-node、プロキシに適用します。既存の独自ホストは初期化時に同じ値を接続します。`hai add` は既存設定を維持します。
|
|
69
68
|
|
|
70
69
|
`captureConsole` と `persistDraft` の既定値は false です。永続化にはホストが決定する非認証情報のユーザー/テナント `persistenceKey` が必要です。`keyScreenshots` は既定で true ですが `screenshot` 全体スイッチに従います。`maxKeyScreenshots` は既定 3、上限 5、`keyScreenshotThrottleMs` は既定 10000 ミリ秒です。
|
|
71
70
|
|
|
72
71
|
自動画像は機密部分を隠したクローン DOM から SVG foreignObject を通して可視ページを再構築します。クロスオリジン画像、iframe/video、未準備のネットワークフォントは欠落する場合があります。ブラウザーや CSP 制限による失敗は missing として記録され、画像の手動添付も利用できます。
|
|
73
72
|
|
|
73
|
+
### フィードバック収集と配布確認
|
|
74
|
+
|
|
75
|
+
現在のページ画像を先頭に表示します。`captureScreenshot(attachmentId?)` は現在のページを追加し、IDを渡すと既存画像を置換します。`FeedbackModal.oncapture` に接続すると、入力を保持して画面を閉じ、ページを切り替えて撮影できます。`FeedbackForm.evidence` は脱敏済みログ、リクエスト、環境と操作記録を展開表示し、`evidenceSummary` は不足理由を補足します。ブラウザー環境は実際のURL(クエリとhashを除外)、画面、言語とビルドから取得します。サーバーは `envAllowlist` の非認証情報だけを収集し、未設定値は null です。
|
|
76
|
+
|
|
77
|
+
対応ブラウザーでは「画面録画 → 録画を開始」からタブやウィンドウを選択し、最大60秒 / 5 MiBの無音動画を低フレームレート・低ビットレートで録画できます。共有停止、取消、時間切れとコンポーネント破棄でストリームを解放します。動画は選択した画面そのもので自動マスクされません。送信前に確認・削除できます。WebM/MP4も非公開保存、チェックサムと5 MiB制限に従い、非対応環境では動画ファイルを添付できます。
|
|
78
|
+
|
|
79
|
+
管理台とfeedback有効のfullstackテンプレートは `replay: true` を明示し、SDKの既定は無効のままです。管理者は詳細の「ページ再生」で操作を再生・移動できます。宿主の `onreplay` は `kind: replay` の全ページを取得してください。未記録時は理由を表示し、録画は操作と添付で確認できます。ローカル保存後は `hai feedback view <zip>` で画像、録画と操作を表示できます。未取得のサーバー資料は不足として記録します。
|
|
80
|
+
|
|
81
|
+
`saveDraft(input?)` は Web ダウンロード、Tauri 保存ダイアログ、Capacitor のキャッシュ/ネイティブ共有を統一します。取消は `status: 'cancelled'`、失敗は失敗として返します。ネイティブプラグインは optional peer で、生成 App/Desktop に依存と権限を設定済みです。独自ホストは対応プラグインを導入してください。`createFeedbackReview(typedClient.feedback.reports)` は `loadReplay(id)` と取消可能な `exportReport(id)` を提供します。ログアウト時は `review.close()` と `client.close({ discard: true })` で収集・ポーリング、プレビューと永続草稿を終了・削除します。
|
|
82
|
+
|
|
83
|
+
ビルド識別子は一律 128 文字までです。明示した無効な HAI_BUILD_ID/HAI_BUILD_COMMIT は値を再表示せずビルドで拒否します。verificationCode、otpCode などを既定で脱機密化し、すべての業務 code を隠すことはしません。
|
|
84
|
+
|
|
85
|
+
サーバーポリシー取得失敗時も有界のローカル収集・出力を継続し server-policy-unavailable を示します。オンライン送信前に再確認します。addFiles(files, removedAttachmentIds?) は旧添付削除と追加を原子的に処理し、表示と実容量を一致させます。
|
|
86
|
+
|
|
87
|
+
console/disabled は Reach パッケージ不要です。reach モードは optional peer の @h-ai/reach を明示導入・初期化します。認証通知は生成 receiptId のみを返し、Provider messageId を保存・返却しません。
|
|
88
|
+
|
|
89
|
+
Web クライアントは原生プラグインを解決・導入しません。独自ホストは @h-ai/feedback/tauri の saveTauriFeedbackFile または @h-ai/feedback/capacitor の saveCapacitorFeedbackFile を読み込み、起動時に configureFeedbackSaving(saver) を呼びます。生成工程には接続済みで、業務コードはネイティブファイルの管理を行いません。
|
|
90
|
+
|
|
74
91
|
### 一覧、処理、出力
|
|
75
92
|
|
|
76
93
|
`list/get/getEvidence` は認可された報告・詳細・証拠ページを返します。`setStatus({ id, status, expectedVersion }, context)` で処理と取り消しの競合を防ぎます。`retry` は回復可能な操作のみ再登録します。`export` は証拠の版を固定し、`getExport` は完成済みZIPを返します。欠落や切り捨ては明示され、ページ再生は業務処理の再実行ではありません。
|
|
@@ -92,7 +109,7 @@ hai feedback view feedback-FB-1.zip
|
|
|
92
109
|
|
|
93
110
|
### 設定とエラー
|
|
94
111
|
|
|
95
|
-
`FeedbackConfigSchema` は appId、recipients、notificationProvider、adminUrl、collectionTimeoutMs、workerIntervalMs、leaseMs、maxAttempts、retentionDays、envAllowlist と資料の容量を検証します。YAML の環境参照は `HAI_FEEDBACK_*` を使い、ブラウザーへ SMTP/S3 の秘密を公開しません。
|
|
112
|
+
`FeedbackConfigSchema` は appId、notificationMode、recipients、maxTotalBytes、notificationProvider、adminUrl、collectionTimeoutMs、workerIntervalMs、leaseMs、maxAttempts、retentionDays、envAllowlist と資料の容量を検証します。YAML の環境参照は `HAI_FEEDBACK_*` を使い、ブラウザーへ SMTP/S3 の秘密を公開しません。
|
|
96
113
|
|
|
97
114
|
各業務結果の `success` を確認してください。`HaiFeedbackError` は初期化、設定、権限、存在しない記録、競合、容量、checksum エラーを表します。HTTP 200やSMTP受理だけでは、業務成功や受信箱への配信を証明できません。不確実な送信は重複する可能性があります。
|
|
98
115
|
|
package/README.md
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
- 有界采集前后端日志、请求、错误、操作、版本与白名单环境信息;默认错误前后各最多 100 条。
|
|
14
14
|
- 数据库事务受理、提交/证据幂等、附件校验、独立的处理/采集/通知状态、持久化任务与租约恢复。
|
|
15
|
-
-
|
|
15
|
+
- 通知由 `notificationMode` 独立选择:`disabled` 不创建通知任务;`console` 在生产也可输出已脱敏的反馈摘要,不依赖 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
|
|
|
19
19
|
## 使用入门
|
|
20
20
|
|
|
21
|
-
服务端先初始化
|
|
21
|
+
服务端先初始化 core、reldb 和私有 storage,再初始化 feedback;仅 reach 通知模式需先初始化 Reach。生产使用共享数据库和私有对象存储,连接凭据由宿主管理。
|
|
22
22
|
|
|
23
23
|
以下片段位于异步函数内,依赖已经初始化;`context` 必须由可信服务端认证和授权生成:
|
|
24
24
|
|
|
@@ -28,8 +28,7 @@ import { feedback } from '@h-ai/feedback'
|
|
|
28
28
|
const initialized = await feedback.init({
|
|
29
29
|
appId: 'orders',
|
|
30
30
|
appVersion: '1.0.0',
|
|
31
|
-
|
|
32
|
-
notificationProvider: 'email',
|
|
31
|
+
notificationMode: 'console',
|
|
33
32
|
adminUrl: 'https://app.example.test/admin/feedback',
|
|
34
33
|
})
|
|
35
34
|
if (!initialized.success)
|
|
@@ -57,22 +56,40 @@ return submitted
|
|
|
57
56
|
|
|
58
57
|
`text/plain` 附件在 5 MiB 文件上限内全文脱敏,保留安全正文和末尾日志,不套用结构化证据的单字段截断。超过上限的原文件在读取前拒绝,脱敏结果仍受同一附件字节上限约束。JSON 附件和结构化证据继续限制字段长度、深度及数量,可能被裁剪;需要完整日志时使用纯文本附件。待提交附件与首次包络冻结后本地导出的新增附件使用相同规则。
|
|
59
58
|
|
|
60
|
-
完整客户端方法为 `init(config)`、`prepare/submit/flush/record/getDraft/annotate/addFiles/exportDraft
|
|
59
|
+
完整客户端方法为 `init(config)`、`prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/close`。将表单 `onfileschange` 连接 `addFiles` 后刷新草稿附件预览,用户便可在提交前标注、遮挡或删除新增图片。`data-feedback-action` 标记关键操作,`data-feedback-private` 遮挡敏感内容,`data-feedback-ignore` 排除反馈界面自身。首次发送后包络冻结;同一提交重试时修改输入会返回冲突并保留输入,可先本地导出。
|
|
61
60
|
|
|
62
61
|
`addFiles` 按批次原子添加:全部文件通过校验才合并,任一失败均保留原附件和缺失标记,不保存半批文件。并发成功批次不会相互覆盖,合并后仍最多 20 项;读取期间草稿切换或首次包络冻结时返回 `CONFLICT`,不会补入包络之外的隐藏上传。
|
|
63
62
|
|
|
64
|
-
`annotate(id,
|
|
63
|
+
`annotate(id, { ...rect, mode, coordinateSpace: 'ratio' })` 直接接受 UI 的 0–1 坐标,由 SDK 按解码后的原图换算,支持小数、小于 1% 的区域、反向拖动、边缘与 CSS 缩放。省略 coordinateSpace 仍使用原图像素。`getView()` 管理并复用预览 URL;替换、删除和关闭时释放。业务无需 Image.decode 或 URL.createObjectURL。
|
|
65
64
|
|
|
66
65
|
并发 `init` 返回 `INIT_IN_PROGRESS`;`close` 等待有界截图退出后清理会话。截图期间关闭使 `prepare` 返回失败,不复活草稿。异步标注完成时若草稿已冻结、关闭、删除图片或被另一标注更新,则返回 `CONFLICT`,不改变已提交附件的字节或 checksum。
|
|
67
66
|
|
|
68
67
|
草稿由 SDK 持有并通过 `annotate`、`addFiles` 等方法更新。Svelte 宿主保存该引用时使用 `$state.raw<FeedbackDraft>()`,操作成功后从 `getDraft()` 刷新附件视图;不要将 SDK 草稿放入深度 `$state` 代理,否则 SDK 替换图片字节后,代理可能继续返回已缓存的旧字节。
|
|
69
68
|
|
|
70
|
-
|
|
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` 用于服务传输、adapter-node 和代理。已有自定义宿主需在同一初始化位置接入此值;`hai add` 保留已有配置。
|
|
71
70
|
|
|
72
71
|
`captureConsole` 和 `persistDraft` 默认关闭;持久化启用时必须提供由宿主确定的非凭据用户/租户 `persistenceKey`。`keyScreenshots` 默认开启,仍受 `screenshot` 总开关控制;`maxKeyScreenshots` 默认 3、硬上限 5,`keyScreenshotThrottleMs` 默认 10000 毫秒。
|
|
73
72
|
|
|
74
73
|
自动截图由脱敏克隆 DOM 经 SVG foreignObject 重建可见页面。跨域图片、iframe、video 和尚未就绪的网络字体可能缺失;浏览器或 CSP 限制导致失败时明确标记 missing,用户仍可手动添加附件。
|
|
75
74
|
|
|
75
|
+
`saveDraft(input?)` 统一本地导出与保存:Web 下载、Tauri 原生保存对话框、Capacitor 缓存文件原生分享。取消返回 `status: 'cancelled'`;错误仍返回失败。原生插件为可选 peer,生成 App/Desktop 已配置依赖与 Tauri 权限;自定义宿主需安装对应插件。`createFeedbackReview(typedClient.feedback.reports)` 提供 `loadReplay(id)` 与可取消的 `exportReport(id)`,不再由应用分页和轮询。退出身份调用 `review.close()` 与 `client.close({ discard: true })`,停止采集、轮询,清理预览和持久草稿。
|
|
76
|
+
|
|
77
|
+
Web 客户端不解析或安装原生插件。自定义 Tauri/Capacitor 宿主分别从 @h-ai/feedback/tauri 或 @h-ai/feedback/capacitor 导入 saveTauriFeedbackFile/saveCapacitorFeedbackFile,并在启动时调用 configureFeedbackSaving(saver);生成工程已接入,业务无需处理原生文件生命周期。
|
|
78
|
+
|
|
79
|
+
### 反馈现场与交付检查
|
|
80
|
+
|
|
81
|
+
当前页面截图优先展示;`captureScreenshot(attachmentId?)` 补充当前页面,传附件 ID 则替换原截图。`FeedbackModal` 的 `oncapture` 接入此方法,选取时隐藏弹窗,允许切换页面后点击“截取当前页面”,并保留说明。`FeedbackForm.evidence` 展开显示已脱敏的日志、请求、环境和回放事件;`evidenceSummary` 补充缺失说明。浏览器环境取实际 URL(去查询参数/hash)、视口、语言和构建标识;服务端环境仅采集 `envAllowlist` 中的非凭据键,缺失值为 null。
|
|
82
|
+
|
|
83
|
+
支持录屏的浏览器可在反馈中选择“录屏 → 开始录屏”,授权页面/窗口后录制无声视频,最多 60 秒 / 5 MiB,低帧率和码率;停止共享、取消、超时和组件销毁均释放流。录屏是用户主动选择的原始画面,不会自动遮挡敏感信息,停止后可预览和删除。WebM/MP4 与其他附件使用相同的私有存储、校验和及 5 MiB 限制;不支持录屏的浏览器可直接添加视频文件。
|
|
84
|
+
|
|
85
|
+
参考管理台和启用 feedback 的 fullstack 模板显式启用 `replay: true`;SDK 默认仍关闭。管理员在反馈详情的“页面回放”播放或定位页面操作,宿主 `onreplay` 须分页读取完整 `kind: replay` 事件,不能只传普通材料的一页。无记录时明确提示;录屏在“操作现场”预览。保存反馈到本地后可用 `hai feedback view <zip>` 查看附件、录屏和回放;本地包明确缺少尚未取得的服务端材料。
|
|
86
|
+
|
|
87
|
+
构建身份统一限制 128 字符;显式非法 HAI_BUILD_ID/HAI_BUILD_COMMIT 在构建时拒绝,错误不回显私密值。默认脱敏包含 verificationCode、otpCode、短信/邮件/一次性/二次验证码;业务 code 字段不会一概隐藏。
|
|
88
|
+
|
|
89
|
+
服务策略暂不可用时,客户端按有界本地预算继续采集并标记 server-policy-unavailable,允许本地导出;在线提交前重新获取有效策略,失败不受理。addFiles(files, removedAttachmentIds?) 在同一原子批次移除已删除的旧附件,容量显示与实际合并一致。
|
|
90
|
+
|
|
91
|
+
console/disabled 无 Reach npm 依赖;选 reach 模式需显式安装 @h-ai/reach(optional peer)并由宿主初始化。凭据通知只返回生成的 receiptId,不保存或回传可能携带载荷的 Provider messageId。
|
|
92
|
+
|
|
76
93
|
### 查询、处理与导出
|
|
77
94
|
|
|
78
95
|
`list/get/getEvidence` 返回受权记录、详情与分页证据;`setStatus({ id, status, expectedVersion }, context)` 以版本检查执行处理/撤销。`retry` 仅重排可恢复项。`export` 固定证据修订并创建任务,通过 `getExport` 获取已生成的 ZIP;导出未完成不伪装成功。材料缺失和截断随包保留,回放不是重新执行业务。
|
|
@@ -94,7 +111,7 @@ hai feedback view feedback-FB-1.zip
|
|
|
94
111
|
|
|
95
112
|
### 配置与错误
|
|
96
113
|
|
|
97
|
-
`FeedbackConfigSchema` 校验所有输入;主要配置为 appId、recipients、notificationProvider、adminUrl、collectionTimeoutMs、workerIntervalMs、leaseMs、maxAttempts、retentionDays、envAllowlist 及附件/证据容量。通过 `HAI_FEEDBACK_*` 显式映射 YAML;浏览器只取得允许公开的采集策略,不能拿到 SMTP/S3 密钥。
|
|
114
|
+
`FeedbackConfigSchema` 校验所有输入;主要配置为 appId、notificationMode、recipients、maxTotalBytes、notificationProvider、adminUrl、collectionTimeoutMs、workerIntervalMs、leaseMs、maxAttempts、retentionDays、envAllowlist 及附件/证据容量。通过 `HAI_FEEDBACK_*` 显式映射 YAML;浏览器只取得允许公开的采集策略,不能拿到 SMTP/S3 密钥。
|
|
98
115
|
|
|
99
116
|
领域错误使用 `HaiFeedbackError`,包括未初始化、配置、权限、找不到、冲突、容量及 checksum 失败。所有领域 API 先检查 `success`;HTTP 200 或 Promise 完成不代表反馈、上传或邮件成功。SMTP 受理不等于管理员收件箱已投递;不确定发送存在重发边界。
|
|
100
117
|
|
package/dist/bundle/index.d.ts
CHANGED
package/dist/bundle/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createFeedbackBundle, getFeedbackBundleViewerFiles, queryFeedbackBundle, readFeedbackBundle, summarizeFeedbackBundle } from '../chunk-
|
|
1
|
+
export { createFeedbackBundle, getFeedbackBundleViewerFiles, queryFeedbackBundle, readFeedbackBundle, summarizeFeedbackBundle } from '../chunk-XQXX5WMV.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { F as FeedbackFileSaver } from './feedback-client-files-B88nCOo_.js';
|
|
2
|
+
import '@h-ai/api-contract/feedback';
|
|
3
|
+
import '@h-ai/core';
|
|
4
|
+
|
|
5
|
+
/** Capacitor 独立入口,只在 App 中导入插件,缓存目录由本次调用负责清理。 */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 以原生分享保存临时诊断包;分享结束、失败或退出会话均清理缓存。
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { configureFeedbackSaving } from '@h-ai/feedback/client'
|
|
12
|
+
* import { saveCapacitorFeedbackFile } from '@h-ai/feedback/capacitor'
|
|
13
|
+
* configureFeedbackSaving(saveCapacitorFeedbackFile)
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare const saveCapacitorFeedbackFile: FeedbackFileSaver;
|
|
17
|
+
|
|
18
|
+
export { saveCapacitorFeedbackFile };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Filesystem, Directory } from '@capacitor/filesystem';
|
|
2
|
+
import { Share } from '@capacitor/share';
|
|
3
|
+
|
|
4
|
+
// src/client/feedback-client-capacitor.ts
|
|
5
|
+
var saveCapacitorFeedbackFile = async (bytes, filename, _contentType, signal) => {
|
|
6
|
+
const path = `feedback/${globalThis.crypto.randomUUID()}/${filename}`;
|
|
7
|
+
const cleanup = () => Filesystem.rmdir({ path: path.slice(0, path.lastIndexOf("/")), directory: Directory.Cache, recursive: true }).catch(() => void 0);
|
|
8
|
+
const abort = () => {
|
|
9
|
+
void cleanup();
|
|
10
|
+
};
|
|
11
|
+
let raw = "";
|
|
12
|
+
for (let offset = 0; offset < bytes.length; offset += 32768)
|
|
13
|
+
raw += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
14
|
+
try {
|
|
15
|
+
if (signal?.aborted)
|
|
16
|
+
return { status: "cancelled" };
|
|
17
|
+
const file = await Filesystem.writeFile({ path, data: btoa(raw), directory: Directory.Cache, recursive: true });
|
|
18
|
+
if (signal?.aborted)
|
|
19
|
+
return { status: "cancelled" };
|
|
20
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
21
|
+
await Share.share({ files: [file.uri], dialogTitle: filename });
|
|
22
|
+
return { status: signal?.aborted ? "cancelled" : "shared" };
|
|
23
|
+
} catch (cause) {
|
|
24
|
+
if (cause instanceof Error && cause.message === "Share canceled")
|
|
25
|
+
return { status: "cancelled" };
|
|
26
|
+
throw cause;
|
|
27
|
+
} finally {
|
|
28
|
+
signal?.removeEventListener("abort", abort);
|
|
29
|
+
await cleanup();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { saveCapacitorFeedbackFile };
|
|
34
|
+
//# sourceMappingURL=capacitor.js.map
|
|
35
|
+
//# sourceMappingURL=capacitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/feedback-client-capacitor.ts"],"names":[],"mappings":";;;;AAcO,IAAM,yBAAA,GAA+C,OAAO,KAAA,EAAO,QAAA,EAAU,cAAc,MAAA,KAAW;AAC3G,EAAA,MAAM,OAAO,CAAA,SAAA,EAAY,UAAA,CAAW,OAAO,UAAA,EAAY,IAAI,QAAQ,CAAA,CAAA;AACnE,EAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,KAAA,CAAM,EAAE,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,IAAA,CAAK,WAAA,CAAY,GAAG,CAAC,CAAA,EAAG,SAAA,EAAW,SAAA,CAAU,KAAA,EAAO,SAAA,EAAW,MAAM,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AACzJ,EAAA,MAAM,QAAQ,MAAM;AAClB,IAAA,KAAK,OAAA,EAAQ;AAAA,EACf,CAAA;AACA,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,MAAA,GAAS,CAAA,EAAG,MAAA,GAAS,KAAA,CAAM,QAAQ,MAAA,IAAU,KAAA;AACpD,IAAA,GAAA,IAAO,MAAA,CAAO,aAAa,GAAG,KAAA,CAAM,SAAS,MAAA,EAAQ,MAAA,GAAS,KAAK,CAAC,CAAA;AACtE,EAAA,IAAI;AACF,IAAA,IAAI,MAAA,EAAQ,OAAA;AACV,MAAA,OAAO,EAAE,QAAQ,WAAA,EAAY;AAC/B,IAAA,MAAM,IAAA,GAAO,MAAM,UAAA,CAAW,SAAA,CAAU,EAAE,IAAA,EAAM,IAAA,EAAM,IAAA,CAAK,GAAG,GAAG,SAAA,EAAW,SAAA,CAAU,KAAA,EAAO,SAAA,EAAW,MAAM,CAAA;AAC9G,IAAA,IAAI,MAAA,EAAQ,OAAA;AACV,MAAA,OAAO,EAAE,QAAQ,WAAA,EAAY;AAC/B,IAAA,MAAA,EAAQ,iBAAiB,OAAA,EAAS,KAAA,EAAO,EAAE,IAAA,EAAM,MAAM,CAAA;AACvD,IAAA,MAAM,KAAA,CAAM,KAAA,CAAM,EAAE,KAAA,EAAO,CAAC,KAAK,GAAG,CAAA,EAAG,WAAA,EAAa,QAAA,EAAU,CAAA;AAC9D,IAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,GAAU,cAAc,QAAA,EAAS;AAAA,EAC5D,SACO,KAAA,EAAO;AAEZ,IAAA,IAAI,KAAA,YAAiB,KAAA,IAAS,KAAA,CAAM,OAAA,KAAY,gBAAA;AAC9C,MAAA,OAAO,EAAE,QAAQ,WAAA,EAAY;AAC/B,IAAA,MAAM,KAAA;AAAA,EACR,CAAA,SACA;AACE,IAAA,MAAA,EAAQ,mBAAA,CAAoB,SAAS,KAAK,CAAA;AAC1C,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AACF","file":"capacitor.js","sourcesContent":["/** Capacitor 独立入口,只在 App 中导入插件,缓存目录由本次调用负责清理。 */\nimport type { FeedbackFileSaver } from './feedback-client-files.js'\nimport { Directory, Filesystem } from '@capacitor/filesystem'\nimport { Share } from '@capacitor/share'\n\n/**\n * 以原生分享保存临时诊断包;分享结束、失败或退出会话均清理缓存。\n * @example\n * ```ts\n * import { configureFeedbackSaving } from '@h-ai/feedback/client'\n * import { saveCapacitorFeedbackFile } from '@h-ai/feedback/capacitor'\n * configureFeedbackSaving(saveCapacitorFeedbackFile)\n * ```\n */\nexport const saveCapacitorFeedbackFile: FeedbackFileSaver = async (bytes, filename, _contentType, signal) => {\n const path = `feedback/${globalThis.crypto.randomUUID()}/${filename}`\n const cleanup = () => Filesystem.rmdir({ path: path.slice(0, path.lastIndexOf('/')), directory: Directory.Cache, recursive: true }).catch(() => undefined)\n const abort = () => {\n void cleanup()\n }\n let raw = ''\n for (let offset = 0; offset < bytes.length; offset += 32768)\n raw += String.fromCharCode(...bytes.subarray(offset, offset + 32768))\n try {\n if (signal?.aborted)\n return { status: 'cancelled' }\n const file = await Filesystem.writeFile({ path, data: btoa(raw), directory: Directory.Cache, recursive: true })\n if (signal?.aborted)\n return { status: 'cancelled' }\n signal?.addEventListener('abort', abort, { once: true })\n await Share.share({ files: [file.uri], dialogTitle: filename })\n return { status: signal?.aborted ? 'cancelled' : 'shared' }\n }\n catch (cause) {\n // 原生插件以固定消息表示主动取消;其余异常交给统一保存边界记录并返回失败。\n if (cause instanceof Error && cause.message === 'Share canceled')\n return { status: 'cancelled' }\n throw cause\n }\n finally {\n signal?.removeEventListener('abort', abort)\n await cleanup()\n }\n}\n"]}
|
|
@@ -89,13 +89,10 @@ var zh_CN_default = {
|
|
|
89
89
|
|
|
90
90
|
// src/feedback-i18n.ts
|
|
91
91
|
var feedbackM = core.i18n.createMessageGetter({ "zh-CN": zh_CN_default, "en-US": en_US_default, "ja-JP": ja_JP_default });
|
|
92
|
-
|
|
93
|
-
// src/feedback-sanitize-text.ts
|
|
94
|
-
var CREDENTIAL_TEXT = /\b(?:Bearer|Basic)\s+[\w.~+/-]+=*|\b(?:[\w-]*[_-])?(?:password|passwd|passcode|secret|token|session(?:[_-]?id)?|authorization|cookie|csrf|credential|api[_-]?key|private[_-]?key|(?:access|refresh|id|csrf)token)(?:\\?["'])?\s*[:=]\s*(?:(?:Bearer|Basic)\s+)?(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\\"(?:[^\\]|\\\\\\\\|\\\\\\"|\\\\[^"\\])*\\"|[^\s,;&}\]]+)/gi;
|
|
95
92
|
function redactFeedbackText(text) {
|
|
96
|
-
return
|
|
93
|
+
return core.sanitize.sanitizeText(text);
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
export { HaiFeedbackError, feedbackM, redactFeedbackText };
|
|
100
|
-
//# sourceMappingURL=chunk-
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-KGHZL7K6.js.map
|
|
98
|
+
//# sourceMappingURL=chunk-KGHZL7K6.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-KGHZL7K6.js","sourcesContent":["import type {\n FeedbackAppendEvidenceInput,\n FeedbackAttachment,\n FeedbackAttachmentData,\n FeedbackBudget,\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'\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 * 反馈服务对象;领域操作返回 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) => 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_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_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_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"]}
|