feat(captcha): Add captcha_cap_asset_server configuration option to support static asset server settings (#2584)

This commit is contained in:
WittF 2025-06-27 23:49:35 +08:00
parent ac6f97d9ba
commit b768bb1410
8 changed files with 89 additions and 6 deletions

View File

@ -437,6 +437,10 @@
"capSiteKeyDes": "The site key from your Cap server dashboard.",
"capSecretKey": "Secret Key",
"capSecretKeyDes": "The secret key from your Cap server dashboard.",
"capAssetServer": "Asset Server Source",
"capAssetServerDes": "Choose the source for loading Cap captcha static assets. Using self-deployed server requires setting ENABLE_ASSETS_SERVER=true environment variable to <0>enable asset server</0>.",
"capAssetServerCdn": "Default public CDN",
"capAssetServerInstance": "Self-deployed server",
"captchaProvider": "Captcha provider",
"captchaWidth": "Width",
"captchaHeight": "Height",

View File

@ -435,6 +435,10 @@
"capSiteKeyDes": "Cap サーバーダッシュボードから取得したサイトキー。",
"capSecretKey": "シークレットキー",
"capSecretKeyDes": "Cap サーバーダッシュボードから取得したシークレットキー。",
"capAssetServer": "静的リソースソース",
"capAssetServerDes": "Cap認証コードの静的リソースの読み込みソースを選択します。自己デプロイサーバーを使用するにはサーバー側で環境変数 ENABLE_ASSETS_SERVER=true を設定して<0>静的リソースサービスを有効にする</0>必要があります。",
"capAssetServerCdn": "デフォルト公共CDN",
"capAssetServerInstance": "自己デプロイサーバー",
"captchaProvider": "認証コードタイプ",
"captchaWidth": "幅",
"captchaHeight": "高さ",

View File

@ -430,11 +430,15 @@
"turnstileSiteKSecret": "密钥",
"cap": "Cap",
"capInstanceURL": "实例 URL",
"capInstanceURLDes": "自部署 Cap 服务器的 URL 地址。详细信息请参考 <0>独立模式文档</0>。",
"capInstanceURLDes": "自部署 Cap 服务器的 URL 地址。详细信息请参考 <0>自部署模式文档</0>。",
"capSiteKey": "站点密钥",
"capSiteKeyDes": "从 Cap 服务器控制面板获取的站点密钥。",
"capSecretKey": "私密密钥",
"capSecretKeyDes": "从 Cap 服务器控制面板获取的私密密钥。",
"capAssetServer": "静态资源服务源",
"capAssetServerDes": "选择 Cap 验证码静态资源的加载源。使用自部署服务器需要在服务器端设置环境变量 ENABLE_ASSETS_SERVER=true <0>开启静态资源服务</0>。",
"capAssetServerCdn": "默认境外 CDN",
"capAssetServerInstance": "自部署服务器",
"captchaProvider": "验证码类型",
"captchaWidth": "宽度",
"captchaHeight": "高度",

View File

@ -432,6 +432,10 @@
"capSiteKeyDes": "從 Cap 伺服器控制面板獲取的站點金鑰。",
"capSecretKey": "私密金鑰",
"capSecretKeyDes": "從 Cap 伺服器控制面板獲取的私密金鑰。",
"capAssetServer": "靜態資源服務源",
"capAssetServerDes": "選擇 Cap 驗證碼靜態資源的載入源。使用自部署伺服器需要在伺服器端設定環境變數 ENABLE_ASSETS_SERVER=true <0>開啟靜態資源服務</0>。",
"capAssetServerCdn": "預設公共 CDN",
"capAssetServerInstance": "自部署伺服器",
"captchaProvider": "驗證碼型別",
"captchaWidth": "寬度",
"captchaHeight": "高度",

View File

@ -26,6 +26,7 @@ export interface SiteConfig {
captcha_cap_instance_url?: string;
captcha_cap_site_key?: string;
captcha_cap_secret_key?: string;
captcha_cap_asset_server?: string;
register_enabled?: boolean;
logo?: string;
logo_light?: string;

View File

@ -1,7 +1,8 @@
import { Trans, useTranslation } from "react-i18next";
import { FormControl, Link, Stack } from "@mui/material";
import { FormControl, Link, Stack, ListItemText } from "@mui/material";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx";
import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx";
import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
import * as React from "react";
import { NoMarginHelperText } from "../Settings.tsx";
@ -78,6 +79,50 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
</NoMarginHelperText>
</FormControl>
</SettingForm>
<SettingForm title={t("settings.capAssetServer")} lgWidth={5}>
<FormControl>
<DenseSelect
value={values.captcha_cap_asset_server || "cdn"}
onChange={(e) =>
setSettings({
captcha_cap_asset_server: e.target.value,
})
}
>
<SquareMenuItem value="cdn">
<ListItemText
slotProps={{
primary: { variant: "body2" },
}}
>
{t("settings.capAssetServerCdn")}
</ListItemText>
</SquareMenuItem>
<SquareMenuItem value="instance">
<ListItemText
slotProps={{
primary: { variant: "body2" },
}}
>
{t("settings.capAssetServerInstance")}
</ListItemText>
</SquareMenuItem>
</DenseSelect>
<NoMarginHelperText>
<Trans
i18nKey="settings.capAssetServerDes"
ns={"dashboard"}
components={[
<Link
key={0}
href={"https://capjs.js.org/guide/standalone/options.html#asset-server"}
target={"_blank"}
/>,
]}
/>
</NoMarginHelperText>
</FormControl>
</SettingForm>
</Stack>
);
};

View File

@ -224,6 +224,7 @@ const Settings = () => {
"captcha_cap_instance_url",
"captcha_cap_site_key",
"captcha_cap_secret_key",
"captcha_cap_asset_server",
]}
>
<Captcha />

View File

@ -22,6 +22,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url);
const capSiteKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_site_key);
const capAssetServer = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_asset_server);
// Keep callback reference up to date
useEffect(() => {
@ -132,6 +133,14 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const initWidget = () => {
scriptLoadedRef.current = true;
// 根据配置设置WASM URL保持资源加载的一致性
if (capAssetServer === "instance") {
(window as any).CAP_CUSTOM_WASM_URL = `${capInstanceURL.replace(/\/$/, "")}/assets/cap_wasm_bg.wasm`;
} else {
(window as any).CAP_CUSTOM_WASM_URL = "https://cdn.jsdelivr.net/npm/@captcha/widget/dist/cap_wasm_bg.wasm";
}
// Add a small delay to ensure DOM is ready
setTimeout(() => {
createWidget();
@ -141,11 +150,22 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
if (!script) {
script = document.createElement("script");
script.id = scriptId;
script.src = `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`;
// 根据配置选择静态资源源
const assetSource =
capAssetServer === "instance"
? `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`
: "https://cdn.jsdelivr.net/npm/@captcha/widget/dist/widget.js";
script.src = assetSource;
script.async = true;
script.onload = initWidget;
script.onerror = () => {
console.error("Failed to load Cap widget script");
if (capAssetServer === "instance") {
console.error("Failed to load Cap widget script from instance server");
} else {
console.error("Failed to load Cap widget script from jsDelivr CDN");
}
};
document.head.appendChild(script);
} else if (scriptLoadedRef.current || (window as any).Cap) {
@ -166,7 +186,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
captchaRef.current.innerHTML = "";
}
};
}, [capInstanceURL, capSiteKey, t]);
}, [capInstanceURL, capSiteKey, capAssetServer, t]);
if (!capInstanceURL || !capSiteKey) {
return null;