-
-
- {t("deeplink.usageScript", {
- defaultValue: "用量查询",
- })}
-
-
- {/*
+ {(request.usageScript ||
+ request.usageEnabled !== undefined) && (
+
+
+ {t("deeplink.usageScript", {
+ defaultValue: "用量查询",
+ })}
+
+
+ {/*
判据是 `=== true`,与后端 `usage_enabled.unwrap_or(false)`
严格对齐。此前用的 `!== false` 会把"链接没说"渲染成绿色的
「已启用」——徽章必须显示实际会发生的事,不能比后端更乐观。
*/}
-
- {request.usageEnabled === true
- ? t("deeplink.usageScriptEnabled", {
- defaultValue: "已启用",
- })
- : t("deeplink.usageScriptDisabled", {
- defaultValue: "未启用",
- })}
-
+
+ {request.usageEnabled === true
+ ? t("deeplink.usageScriptEnabled", {
+ defaultValue: "已启用",
+ })
+ : t("deeplink.usageScriptDisabled", {
+ defaultValue: "未启用",
+ })}
+
+
-
+ )}
- {/*
+ {request.usageScript && (
+ <>
+ {/*
脚本正文必须完整展示。这段是会执行的 JavaScript,而 payload
常常整条藏在中间——`whitespace-pre-wrap break-all` + 可滚动容器,
不用 truncate,任何字符都不得被 CSS 藏起来。
*/}
-
-
- {t("deeplink.usageScriptCode")}
-
-
- {decodeDeeplinkPayload(
- request.usageScript,
- decodeBase64Utf8,
- )}
-
-
+
+
+ {t("deeplink.usageScriptCode")}
+
+
+ {decodeDeeplinkPayload(
+ request.usageScript,
+ decodeBase64Utf8,
+ )}
+
+
- {/*
+ {/*
无条件显示,不看 `usageEnabled`:代码无论启用与否都会被写入供应商
配置,用户之后在应用内一键即可开启。挂条件等于让攻击者省略参数就能
关掉这条警告。
*/}
-
- ⚠️
- {t("deeplink.usageScriptWarning")}
-
+
+ ⚠️
+ {t("deeplink.usageScriptWarning")}
+
+ >
+ )}
{/* Usage API Key (if different from provider) */}
{request.usageApiKey &&
@@ -682,6 +701,36 @@ export function DeepLinkImportDialog() {
)}
+ {/* Usage Access Token (if present) */}
+ {request.usageAccessToken && (
+
+
+ {t("deeplink.usageAccessToken", {
+ defaultValue: "用量访问令牌",
+ })}
+
+
+ {request.usageAccessToken.length > 4
+ ? `${request.usageAccessToken.substring(0, 4)}${"*".repeat(12)}`
+ : "****"}
+
+
+ )}
+
+ {/* Usage User ID (if present) */}
+ {request.usageUserId && (
+
+
+ {t("deeplink.usageUserId", {
+ defaultValue: "用量用户 ID",
+ })}
+
+
+ {request.usageUserId}
+
+
+ )}
+
{/* Auto Query Interval */}
{request.usageAutoInterval &&
request.usageAutoInterval > 0 && (
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 4bce650e2..d0742e1b5 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -2512,6 +2512,8 @@
"usageScriptWarning": "This is JavaScript that runs when usage is queried, once enabled. Import it only if you trust the source.",
"usageApiKey": "Usage API Key",
"usageBaseUrl": "Usage Query URL",
+ "usageAccessToken": "Usage Access Token",
+ "usageUserId": "Usage User ID",
"usageAutoInterval": "Auto Query",
"usageAutoIntervalValue": "Every {{minutes}} minutes",
"risk": {
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index 2a1e9ed21..6695c77bc 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -2512,6 +2512,8 @@
"usageScriptWarning": "これは有効化すると使用量クエリ時に実行される JavaScript です。提供元が信頼できる場合のみインポートしてください。",
"usageApiKey": "使用量 API キー",
"usageBaseUrl": "使用量クエリ URL",
+ "usageAccessToken": "使用量アクセストークン",
+ "usageUserId": "使用量ユーザー ID",
"usageAutoInterval": "自動クエリ",
"usageAutoIntervalValue": "{{minutes}} 分ごと",
"risk": {
diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json
index 945e5b8be..8e7396afe 100644
--- a/src/i18n/locales/zh-TW.json
+++ b/src/i18n/locales/zh-TW.json
@@ -2513,6 +2513,8 @@
"usageScriptWarning": "這是一段 JavaScript 程式碼,啟用後會在查詢用量時執行。請確認來源可信後再匯入。",
"usageApiKey": "用量 API Key",
"usageBaseUrl": "用量查詢位址",
+ "usageAccessToken": "用量存取權杖",
+ "usageUserId": "用量使用者 ID",
"usageAutoInterval": "自動查詢",
"usageAutoIntervalValue": "每 {{minutes}} 分鐘",
"risk": {
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index 98e512b22..570b6779b 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -2512,6 +2512,8 @@
"usageScriptWarning": "这是一段 JavaScript 代码,启用后会在查询用量时执行。请确认来源可信后再导入。",
"usageApiKey": "用量 API Key",
"usageBaseUrl": "用量查询地址",
+ "usageAccessToken": "用量访问令牌",
+ "usageUserId": "用量用户 ID",
"usageAutoInterval": "自动查询",
"usageAutoIntervalValue": "每 {{minutes}} 分钟",
"risk": {
diff --git a/tests/components/DeepLinkImportDialog.test.tsx b/tests/components/DeepLinkImportDialog.test.tsx
new file mode 100644
index 000000000..eeb6d676a
--- /dev/null
+++ b/tests/components/DeepLinkImportDialog.test.tsx
@@ -0,0 +1,102 @@
+import { act, render, screen, waitFor } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { DeepLinkImportDialog } from "@/components/DeepLinkImportDialog";
+import { emitTauriEvent } from "../msw/tauriMocks";
+
+vi.mock("@/components/ui/dialog", () => ({
+ Dialog: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogContent: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogHeader: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogTitle: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogDescription: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ DialogFooter: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+}));
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+describe("DeepLinkImportDialog", () => {
+ it("renders masked usage access token and user id for provider imports", async () => {
+ render(
, { wrapper: Wrapper });
+
+ act(() => {
+ emitTauriEvent("deeplink-import", {
+ version: "v1",
+ resource: "provider",
+ app: "claude",
+ name: "Test Provider",
+ homepage: "https://example.com",
+ endpoint: "https://api.example.com",
+ apiKey: "sk-provider-key",
+ usageEnabled: true,
+ usageScript: btoa("console.log('usage');"),
+ usageApiKey: "sk-usage-key",
+ usageBaseUrl: "https://usage.example.com",
+ usageAccessToken: "pat-secret-token",
+ usageUserId: "user-12345",
+ usageAutoInterval: 60,
+ });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText("用量访问令牌")).toBeInTheDocument();
+ });
+
+ expect(screen.getByText("用量用户 ID")).toBeInTheDocument();
+ expect(screen.getByText("user-12345")).toBeInTheDocument();
+ // Masked: first 4 chars + 12 stars
+ expect(screen.getByText("pat-************")).toBeInTheDocument();
+ });
+
+ it("shows usage credentials even when the deeplink carries no usageScript", async () => {
+ // 后端 build_provider_meta 在任一 usage 字段存在时即持久化(含 access_token
+ // 与 user_id)。若对话框只在 usageScript 存在时开门,这条链接会把凭据静默
+ // 写进供应商配置。撤销门槛 widening(恢复只按 usageScript 开门)本测试即失败。
+ render(
, { wrapper: Wrapper });
+
+ act(() => {
+ emitTauriEvent("deeplink-import", {
+ version: "v1",
+ resource: "provider",
+ app: "claude",
+ name: "Token Only Provider",
+ homepage: "https://example.com",
+ endpoint: "https://api.example.com",
+ apiKey: "sk-provider-key",
+ usageAccessToken: "pat-secret-token",
+ usageUserId: "user-12345",
+ });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText("用量访问令牌")).toBeInTheDocument();
+ });
+
+ expect(screen.getByText("pat-************")).toBeInTheDocument();
+ expect(screen.getByText("用量用户 ID")).toBeInTheDocument();
+ expect(screen.getByText("user-12345")).toBeInTheDocument();
+ // 没有脚本就不应渲染脚本执行警告与脚本代码区
+ expect(
+ screen.queryByText(
+ "这是一段 JavaScript 代码,启用后会在查询用量时执行。请确认来源可信后再导入。",
+ ),
+ ).not.toBeInTheDocument();
+ expect(screen.queryByText("脚本代码")).not.toBeInTheDocument();
+ });
+});