From 4a71be0829f432873d90a882fcb2181358a2556c Mon Sep 17 00:00:00 2001 From: bowen628 Date: Wed, 4 Mar 2026 21:03:21 +0800 Subject: [PATCH] feat(remote): update notices --- .../desktop/src/api/remote_connect_api.rs | 78 ++++++++++++++++- src/apps/desktop/src/lib.rs | 2 + .../src/app/components/NavPanel/NavPanel.scss | 70 ++++++++++++++++ .../components/PersistentFooterActions.tsx | 84 ++++++++++++++++++- .../RemoteConnectDialog.scss | 25 +++++- .../RemoteConnectDialog.tsx | 73 +++++++++++++++- .../api/service-api/RemoteConnectAPI.ts | 23 +++++ src/web-ui/src/locales/en-US/common.json | 37 +++++++- src/web-ui/src/locales/zh-CN/common.json | 37 +++++++- 9 files changed, 411 insertions(+), 18 deletions(-) diff --git a/src/apps/desktop/src/api/remote_connect_api.rs b/src/apps/desktop/src/api/remote_connect_api.rs index 2486875..4d43c80 100644 --- a/src/apps/desktop/src/api/remote_connect_api.rs +++ b/src/apps/desktop/src/api/remote_connect_api.rs @@ -1,12 +1,14 @@ //! Tauri commands for Remote Connect. use bitfun_core::service::remote_connect::{ - bot::BotConfig, ConnectionMethod, ConnectionResult, PairingState, RemoteConnectConfig, + bot::BotConfig, lan, ConnectionMethod, ConnectionResult, PairingState, RemoteConnectConfig, RemoteConnectService, }; use once_cell::sync::OnceCell; +use regex::Regex; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use std::process::Command; use std::sync::Arc; use tokio::sync::RwLock; @@ -220,6 +222,65 @@ pub struct DeviceInfo { pub mac_address: String, } +#[derive(Debug, Serialize)] +pub struct LanNetworkInfo { + pub local_ip: String, + pub gateway_ip: Option, +} + +fn detect_default_gateway_ip() -> Option { + #[cfg(target_os = "macos")] + { + let output = Command::new("route") + .args(["-n", "get", "default"]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let stdout = String::from_utf8_lossy(&output.stdout); + let re = Regex::new(r"(?m)^\s*gateway:\s*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s*$").ok()?; + return re + .captures(&stdout) + .and_then(|c| c.get(1).map(|m| m.as_str().to_string())); + } + + #[cfg(target_os = "linux")] + { + let output = Command::new("ip") + .args(["route", "show", "default"]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let stdout = String::from_utf8_lossy(&output.stdout); + let re = Regex::new(r"(?m)^default\s+via\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\b").ok()?; + return re + .captures(&stdout) + .and_then(|c| c.get(1).map(|m| m.as_str().to_string())); + } + + #[cfg(target_os = "windows")] + { + let output = Command::new("route").args(["print", "-4"]).output().ok()?; + if !output.status.success() { + return None; + } + let stdout = String::from_utf8_lossy(&output.stdout); + let re = Regex::new( + r"(?m)^\s*0\.0\.0\.0\s+0\.0\.0\.0\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+", + ) + .ok()?; + return re + .captures(&stdout) + .and_then(|c| c.get(1).map(|m| m.as_str().to_string())); + } + + #[allow(unreachable_code)] + None +} + // ── Tauri Commands ───────────────────────────────────────────────── #[tauri::command] @@ -236,6 +297,21 @@ pub async fn remote_connect_get_device_info() -> Result { }) } +#[tauri::command] +pub async fn remote_connect_get_lan_ip() -> Result { + lan::get_local_ip().map_err(|e| format!("get local ip: {e}")) +} + +#[tauri::command] +pub async fn remote_connect_get_lan_network_info() -> Result { + let local_ip = lan::get_local_ip().map_err(|e| format!("get local ip: {e}"))?; + let gateway_ip = detect_default_gateway_ip(); + Ok(LanNetworkInfo { + local_ip, + gateway_ip, + }) +} + #[tauri::command] pub async fn remote_connect_get_methods() -> Result, String> { ensure_service().await?; diff --git a/src/apps/desktop/src/lib.rs b/src/apps/desktop/src/lib.rs index 4e0af01..437dd7d 100644 --- a/src/apps/desktop/src/lib.rs +++ b/src/apps/desktop/src/lib.rs @@ -557,6 +557,8 @@ pub async fn run() { i18n_set_config, // Remote Connect api::remote_connect_api::remote_connect_get_device_info, + api::remote_connect_api::remote_connect_get_lan_ip, + api::remote_connect_api::remote_connect_get_lan_network_info, api::remote_connect_api::remote_connect_get_methods, api::remote_connect_api::remote_connect_start, api::remote_connect_api::remote_connect_stop, diff --git a/src/web-ui/src/app/components/NavPanel/NavPanel.scss b/src/web-ui/src/app/components/NavPanel/NavPanel.scss index 9eda69e..083bae4 100644 --- a/src/web-ui/src/app/components/NavPanel/NavPanel.scss +++ b/src/web-ui/src/app/components/NavPanel/NavPanel.scss @@ -620,6 +620,76 @@ $_section-header-height: 24px; } } +.bitfun-nav-panel__remote-disclaimer { + display: flex; + flex-direction: column; + gap: 10px; + color: var(--color-text-secondary); + padding: 12px 0 14px; +} + +.bitfun-nav-panel__remote-disclaimer-text { + margin: 0; + font-size: 12px; + line-height: 1.2; + color: var(--color-text-muted); +} + +.bitfun-nav-panel__remote-disclaimer-list { + margin: 0; + padding-left: 20px; + display: flex; + flex-direction: column; + gap: 0; + + li { + font-size: 12px; + line-height: 1.2; + color: var(--color-text-secondary); + padding-right: 0; + } +} + +.bitfun-nav-panel__remote-disclaimer-actions { + display: flex; + justify-content: center; + gap: 12px; + margin-top: 6px; + padding-top: 10px; + border-top: 1px solid var(--border-subtle); +} + +.bitfun-nav-panel__remote-disclaimer-btn { + border-radius: 6px; + border: 1px solid transparent; + min-width: 112px; + padding: 7px 16px; + font-size: 12px; + cursor: pointer; + transition: all $motion-fast $easing-standard; +} + +.bitfun-nav-panel__remote-disclaimer-btn--secondary { + background: var(--element-bg-subtle); + color: var(--color-text-secondary); + border-color: var(--border-subtle); + + &:hover:not(:disabled) { + color: var(--color-text-primary); + border-color: var(--border-medium); + } +} + +.bitfun-nav-panel__remote-disclaimer-btn--primary { + background: var(--color-accent-600, #2563eb); + color: #fff; + border-color: color-mix(in srgb, var(--color-accent-700, #1d4ed8) 35%, transparent); + + &:hover:not(:disabled) { + background: var(--color-accent-700, #1d4ed8); + } +} + // ────────────────────────────────────────────── // Reduced motion // ────────────────────────────────────────────── diff --git a/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx b/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx index efd6ff3..1d2f8b3 100644 --- a/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx +++ b/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx @@ -1,6 +1,6 @@ import React, { useState, useCallback } from 'react'; import { Settings, Info, MoreVertical, PictureInPicture2, Wifi } from 'lucide-react'; -import { Tooltip } from '@/component-library'; +import { Tooltip, Modal } from '@/component-library'; import { useI18n } from '@/infrastructure/i18n/hooks/useI18n'; import { useSceneManager } from '../../../hooks/useSceneManager'; import { useToolbarModeContext } from '@/flow_chat/components/toolbar-mode/ToolbarModeContext'; @@ -10,6 +10,16 @@ import NotificationButton from '../../TitleBar/NotificationButton'; import { AboutDialog } from '../../AboutDialog'; import { RemoteConnectDialog } from '../../RemoteConnectDialog'; +const REMOTE_CONNECT_DISCLAIMER_KEY = 'bitfun:remote-connect:disclaimer-agreed:v1'; + +const getRemoteDisclaimerAgreed = (): boolean => { + try { + return localStorage.getItem(REMOTE_CONNECT_DISCLAIMER_KEY) === 'true'; + } catch { + return false; + } +}; + const PersistentFooterActions: React.FC = () => { const { t } = useI18n('common'); const { openScene } = useSceneManager(); @@ -21,6 +31,8 @@ const PersistentFooterActions: React.FC = () => { const [menuClosing, setMenuClosing] = useState(false); const [showAbout, setShowAbout] = useState(false); const [showRemoteConnect, setShowRemoteConnect] = useState(false); + const [showRemoteDisclaimer, setShowRemoteDisclaimer] = useState(false); + const [hasAgreedRemoteDisclaimer, setHasAgreedRemoteDisclaimer] = useState(() => getRemoteDisclaimerAgreed()); const closeMenu = useCallback(() => { setMenuClosing(true); @@ -53,14 +65,33 @@ const PersistentFooterActions: React.FC = () => { enableToolbarMode(); }; - const handleRemoteConnect = () => { + const handleRemoteConnect = useCallback(async () => { if (!hasWorkspace) { warning(t('header.remoteConnectRequiresWorkspace')); return; } + closeMenu(); + + if (hasAgreedRemoteDisclaimer || getRemoteDisclaimerAgreed()) { + setHasAgreedRemoteDisclaimer(true); + setShowRemoteConnect(true); + return; + } + + setShowRemoteDisclaimer(true); + }, [hasWorkspace, warning, t, closeMenu, hasAgreedRemoteDisclaimer]); + + const handleAgreeDisclaimer = useCallback(() => { + try { + localStorage.setItem(REMOTE_CONNECT_DISCLAIMER_KEY, 'true'); + } catch { + // Ignore storage failures and keep in-memory consent for current session. + } + setHasAgreedRemoteDisclaimer(true); + setShowRemoteDisclaimer(false); setShowRemoteConnect(true); - }; + }, []); return (
@@ -140,6 +171,53 @@ const PersistentFooterActions: React.FC = () => { setShowAbout(false)} /> setShowRemoteConnect(false)} /> + setShowRemoteDisclaimer(false)} + title={t('remoteConnect.disclaimerTitle')} + showCloseButton + size="large" + contentInset + > +
+

{t('remoteConnect.disclaimerIntro')}

+
    +
  1. {t('remoteConnect.disclaimerItemBeta')}
  2. +
  3. {t('remoteConnect.disclaimerItemSecurity')}
  4. +
  5. {t('remoteConnect.disclaimerItemEncryption')}
  6. +
  7. {t('remoteConnect.disclaimerItemOpenSource')}
  8. +
  9. {t('remoteConnect.disclaimerItemPrivacy')}
  10. +
  11. {t('remoteConnect.disclaimerItemDataUsage')}
  12. +
  13. {t('remoteConnect.disclaimerItemCredentials')}
  14. +
  15. {t('remoteConnect.disclaimerItemQrCode')}
  16. +
  17. {t('remoteConnect.disclaimerItemNgrok')}
  18. +
  19. {t('remoteConnect.disclaimerItemSelfHosted')}
  20. +
  21. {t('remoteConnect.disclaimerItemNetwork')}
  22. +
  23. {t('remoteConnect.disclaimerItemBot')}
  24. +
  25. {t('remoteConnect.disclaimerItemBotPersistence')}
  26. +
  27. {t('remoteConnect.disclaimerItemMobileBrowser')}
  28. +
  29. {t('remoteConnect.disclaimerItemCompliance')}
  30. +
  31. {t('remoteConnect.disclaimerItemLiability')}
  32. +
+ +
+ + +
+
+
); }; diff --git a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.scss b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.scss index bfc8ad2..a1768b9 100644 --- a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.scss +++ b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.scss @@ -206,6 +206,16 @@ max-width: 420px; } +.bitfun-remote-connect__description-link { + color: var(--color-text-muted); + text-decoration: underline; + cursor: pointer; + + &:hover { + color: var(--color-text-muted); + } +} + .bitfun-remote-connect__error { font-size: 12px; color: var(--color-danger); @@ -276,11 +286,17 @@ transition: all 0.15s ease; &--connect { - background: var(--color-accent); + background: var(--color-accent-600, #2563eb); color: #fff; + border: 1px solid color-mix(in srgb, var(--color-accent-700, #1d4ed8) 35%, transparent); &:hover:not(:disabled) { - opacity: 0.9; + background: var(--color-accent-700, #1d4ed8); + } + + &:focus-visible { + outline: 2px solid color-mix(in srgb, var(--color-accent-500, #3b82f6) 55%, transparent); + outline-offset: 1px; } &:disabled { @@ -326,11 +342,12 @@ } .bitfun-remote-connect__step-link { - color: var(--color-accent); + color: var(--color-text-muted); text-decoration: underline; cursor: pointer; &:hover { - opacity: 0.85; + color: var(--color-text-muted); } } + diff --git a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx index b4bfdba..e856e78 100644 --- a/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx +++ b/src/web-ui/src/app/components/RemoteConnectDialog/RemoteConnectDialog.tsx @@ -35,6 +35,7 @@ const BOT_TABS: { id: BotTab; label: string }[] = [ ]; const NGROK_SETUP_URL = 'https://dashboard.ngrok.com/get-started/setup'; +const RELAY_SERVER_README_URL = 'https://github.com/GCWing/BitFun/blob/main/src/apps/relay-server/README.md'; const methodToNetworkTab = (method: string | null | undefined): NetworkTab | null => { if (!method) return null; @@ -72,6 +73,7 @@ export const RemoteConnectDialog: React.FC = ({ const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const [lanNetworkInfo, setLanNetworkInfo] = useState<{ localIp: string; gatewayIp: string | null } | null>(null); const [customUrl, setCustomUrl] = useState(''); const [tgToken, setTgToken] = useState(''); @@ -156,6 +158,25 @@ export const RemoteConnectDialog: React.FC = ({ }; }, [isOpen, startPolling]); + useEffect(() => { + if (!isOpen || activeGroup !== 'network' || networkTab !== 'lan') return; + let cancelled = false; + const loadLanNetworkInfo = async () => { + const info = await remoteConnectAPI.getLanNetworkInfo(); + if (!cancelled) { + setLanNetworkInfo( + info + ? { localIp: info.local_ip, gatewayIp: info.gateway_ip ?? null } + : null, + ); + } + }; + void loadLanNetworkInfo(); + return () => { + cancelled = true; + }; + }, [isOpen, activeGroup, networkTab]); + // ── Connection handlers ────────────────────────────────────────── const handleConnect = useCallback(async () => { @@ -228,6 +249,10 @@ export const RemoteConnectDialog: React.FC = ({ void systemAPI.openExternal(NGROK_SETUP_URL); }, []); + const handleOpenRelayReadme = useCallback(() => { + void systemAPI.openExternal(RELAY_SERVER_README_URL); + }, []); + // ── Sub-tab disabled logic ─────────────────────────────────────── const isNetworkSubDisabled = (tabId: NetworkTab): boolean => { @@ -287,7 +312,11 @@ export const RemoteConnectDialog: React.FC = ({ )}
- {t('remoteConnect.stateWaiting')} + + {activeGroup === 'bot' + ? t('remoteConnect.stateWaitingBot') + : t('remoteConnect.stateWaiting')} +

{activeGroup === 'bot' ? t('remoteConnect.botHint') : t('remoteConnect.scanHint')} @@ -313,8 +342,48 @@ export const RemoteConnectDialog: React.FC = ({ } return (

+ {networkTab === 'lan' && lanNetworkInfo?.localIp && ( +

+ {t('remoteConnect.currentIp')}: {lanNetworkInfo.localIp} +

+ )} + {networkTab === 'lan' && lanNetworkInfo?.gatewayIp && ( +

+ {t('remoteConnect.gatewayIp')}: {lanNetworkInfo.gatewayIp} +

+ )}

- {t(`remoteConnect.desc_${networkTab}`)} + {networkTab === 'custom_server' ? ( + <> + {t('remoteConnect.desc_custom_server_prefix')} + { if (e.key === 'Enter') handleOpenRelayReadme(); }} + > + {t('remoteConnect.desc_custom_server_link')} + + {t('remoteConnect.desc_custom_server_suffix')} + + ) : networkTab === 'ngrok' ? ( + <> + {t('remoteConnect.desc_ngrok_prefix')} + { if (e.key === 'Enter') handleOpenNgrokSetup(); }} + > + {t('remoteConnect.desc_ngrok_link')} + + {t('remoteConnect.desc_ngrok_suffix')} + + ) : ( + t(`remoteConnect.desc_${networkTab}`) + )}

{networkTab === 'custom_server' && (
diff --git a/src/web-ui/src/infrastructure/api/service-api/RemoteConnectAPI.ts b/src/web-ui/src/infrastructure/api/service-api/RemoteConnectAPI.ts index e589b40..840134f 100644 --- a/src/web-ui/src/infrastructure/api/service-api/RemoteConnectAPI.ts +++ b/src/web-ui/src/infrastructure/api/service-api/RemoteConnectAPI.ts @@ -38,6 +38,11 @@ export interface RemoteConnectStatus { bot_connected: string | null; } +export interface LanNetworkInfo { + local_ip: string; + gateway_ip: string | null; +} + class RemoteConnectAPIService { private adapter = getTransportAdapter(); @@ -50,6 +55,24 @@ class RemoteConnectAPIService { } } + async getLanIp(): Promise { + try { + return await this.adapter.request('remote_connect_get_lan_ip'); + } catch (e) { + log.warn('getLanIp failed', e); + return null; + } + } + + async getLanNetworkInfo(): Promise { + try { + return await this.adapter.request('remote_connect_get_lan_network_info'); + } catch (e) { + log.warn('getLanNetworkInfo failed', e); + return null; + } + } + async getConnectionMethods(): Promise { try { return await this.adapter.request('remote_connect_get_methods'); diff --git a/src/web-ui/src/locales/en-US/common.json b/src/web-ui/src/locales/en-US/common.json index 3d28ba0..c3b1bd7 100644 --- a/src/web-ui/src/locales/en-US/common.json +++ b/src/web-ui/src/locales/en-US/common.json @@ -170,18 +170,27 @@ "botHint": "Send the pairing code to the bot to complete pairing", "connectedHint": "Mobile device connected. You can close this dialog.", "serverUrl": "Server URL", + "currentIp": "Current IP", + "gatewayIp": "Gateway IP", "feishu": "Feishu", "openBot": "Open Bot", "stateIdle": "Ready", "stateWaiting": "Waiting for scan...", + "stateWaitingBot": "Waiting for bot confirmation...", "stateHandshaking": "Handshaking...", "stateVerifying": "Verifying...", "stateConnected": "Connected", "stateDisconnected": "Disconnected", - "desc_lan": "Connect via local network. Both devices must be on the same LAN/WiFi.", + "desc_lan": "Connect via local network. Both devices must be on the same LAN/WiFi. If connection fails, check router security settings (such as AP/client isolation) and make sure proxy/tunneling settings do not route or block LAN traffic (allow direct LAN access) on both devices.", "desc_bitfun_server": "Connect via official BitFun relay server.", - "desc_ngrok": "Connect via ngrok tunnel. First-time use need to install ngrok and config auth.", + "desc_ngrok": "Connect via ngrok tunnel. For first-time use, just install ngrok and configure auth token. No need to start ngrok manually.", + "desc_ngrok_prefix": "Connect via ngrok tunnel. First-time use need to ", + "desc_ngrok_link": "install ngrok", + "desc_ngrok_suffix": " and configure auth token. No need to start ngrok manually.", "desc_custom_server": "Connect via your own self-hosted relay server.", + "desc_custom_server_prefix": "Connect via your own ", + "desc_custom_server_link": "self-hosted relay server", + "desc_custom_server_suffix": ".", "desc_bot": "Connect via Feishu or Telegram bot. Configure your bot credentials below.", "botTgStep1": "Open Telegram and search for @BotFather", "botTgStep2": "Send /newbot and follow the prompts to create a bot", @@ -189,9 +198,29 @@ "botFeishuStep1Prefix": "Go to ", "botFeishuOpenPlatform": "Feishu Open Platform", "botFeishuStep1Suffix": " and create a Custom App", - "botFeishuStep2": "Enable Bot capability and configure event subscriptions", + "botFeishuStep2": "Enable Bot capability and configure Permissions & Scopes and Events & Callbacks", "botFeishuStep3": "Copy App ID and App Secret below", - "openNgrokSetup": "Open ngrok setup page" + "openNgrokSetup": "Open ngrok setup page", + "disclaimerTitle": "Remote Connect Disclaimer", + "disclaimerIntro": "Before enabling Remote Connect, please read and accept the following:", + "disclaimerItemBeta": "Remote Connect is currently in Beta. It may contain undiscovered security vulnerabilities, functional defects, or incompatible changes. Please use it with full awareness of the risks.", + "disclaimerItemSecurity": "Remote Connect enables network communication paths (including but not limited to LAN, third-party relay, self-hosted relay, bot channels, and other pathways). Use it only on trusted devices and networks.", + "disclaimerItemEncryption": "Remote message payloads are protected with end-to-end encryption (X25519 ECDH + AES-256-GCM with ephemeral key pairs per session); relay servers cannot decrypt message content. However, required metadata (such as device name, connection state, service endpoint, and other connection-context details) is not covered by business-message encryption and may still be visible to network paths, service nodes, or other infrastructure.", + "disclaimerItemOpenSource": "BitFun's Remote Connect encryption implementation is fully open-source. You are free to audit the source code to verify its security.", + "disclaimerItemPrivacy": "Do not transmit sensitive content in untrusted environments. Bot platforms, third-party networks, host logging policies, and other external service practices may affect your exposure surface.", + "disclaimerItemDataUsage": "BitFun does not use your Remote Connect message content for model training or commercial data exploitation. Third-party services you choose to use (such as ngrok, Telegram, Feishu, self-hosted infrastructure tooling, and other integrations) may process data under their own terms.", + "disclaimerItemCredentials": "For ngrok, self-hosted relay, Telegram/Feishu bot modes, and other integration pathways, you are responsible for protecting URLs, tokens, app IDs, app secrets, keys, and other credentials. Credential leakage can cause unauthorized access or other security incidents.", + "disclaimerItemQrCode": "QR codes contain connection details including relay address, room ID, public key, and other session identifiers. Do not share screenshots, recordings, or displays of these codes with unrelated parties.", + "disclaimerItemNgrok": "When using ngrok or other third-party tunneling providers, traffic passes through external service links. You should evaluate account security, jurisdiction/compliance constraints, quota limits, service availability, and potential intermediary-link risks.", + "disclaimerItemSelfHosted": "When using a self-hosted relay, you are responsible for host/container security (including but not limited to patching, access control, TLS certificates, port exposure, firewall rules, DDoS/brute-force protection, audit logging, backup/recovery, and other operational safeguards). A compromised or misconfigured server, leaked keys, or other operational failures may lead to service disruption, data leakage, or tampering risks.", + "disclaimerItemNetwork": "Connectivity and stability may be affected by firewall rules, router isolation policies (e.g., AP/client isolation), proxy/tunneling settings, and other network-control policies.", + "disclaimerItemBot": "In bot mode, pairing codes are for current-session binding only. Do not share them with unrelated parties. Other bot-platform behaviors or policy changes may also affect security and availability.", + "disclaimerItemBotPersistence": "Bot pairing details are persisted on the local disk to enable automatic reconnection after restart. If your machine is accessed by unauthorized individuals, compromised by malware, or affected by other system-security incidents, this information could be exploited.", + "disclaimerItemMobileBrowser": "The mobile client loads as a web application in a browser. Browser cache, local storage, page screenshots, system clipboard, and other browser/system features may introduce additional information exposure risks.", + "disclaimerItemCompliance": "Some countries or regions have legal restrictions on encrypted communication, tunneling services, cross-border data transfer, and related activities. Please evaluate compliance with your local laws and regulations, including potential policy changes.", + "disclaimerItemLiability": "Any risks, losses, disputes, or other adverse consequences arising during your use of Remote Connect (including but not limited to data leakage, service interruption, account anomalies, or service unavailability) are your responsibility; BitFun is not liable for resulting issues.", + "disclaimerDecline": "Decline", + "disclaimerAgree": "Agree and Continue" }, "about": { "close": "Close", diff --git a/src/web-ui/src/locales/zh-CN/common.json b/src/web-ui/src/locales/zh-CN/common.json index 61d3fad..b6b5a51 100644 --- a/src/web-ui/src/locales/zh-CN/common.json +++ b/src/web-ui/src/locales/zh-CN/common.json @@ -170,18 +170,27 @@ "botHint": "将配对码发送给机器人完成配对", "connectedHint": "移动设备已连接,可以关闭此对话框。", "serverUrl": "服务器地址", + "currentIp": "当前 IP", + "gatewayIp": "网关 IP", "feishu": "飞书", "openBot": "打开机器人", "stateIdle": "就绪", "stateWaiting": "等待扫码...", + "stateWaitingBot": "等待机器人确认...", "stateHandshaking": "握手中...", "stateVerifying": "验证中...", "stateConnected": "已连接", "stateDisconnected": "已断开", - "desc_lan": "通过局域网连接,两台设备需在同一LAN/WiFi下。", + "desc_lan": "通过局域网连接,两台设备需在同一LAN/WiFi下。若连接失败,请检查路由器安全设置(如AP隔离/客户端隔离)是否限制局域网互访,并确认两台设备的代理服务设置未影响局域网流量(允许直连局域网)。", "desc_bitfun_server": "通过BitFun官方中继服务器连接。", - "desc_ngrok": "通过ngrok隧道连接,首次使用需安装ngrok并完成授权。", + "desc_ngrok": "通过ngrok隧道连接,首次使用需安装ngrok并完成授权,无需手动启动。", + "desc_ngrok_prefix": "通过ngrok隧道连接,首次使用需", + "desc_ngrok_link": "安装ngrok", + "desc_ngrok_suffix": "并完成授权,无需手动启动。", "desc_custom_server": "通过自建中继服务器连接。", + "desc_custom_server_prefix": "通过", + "desc_custom_server_link": "自建中继服务器连接", + "desc_custom_server_suffix": "。", "desc_bot": "通过飞书或Telegram机器人连接,请先配置机器人凭证。", "botTgStep1": "打开Telegram,搜索 @BotFather", "botTgStep2": "发送 /newbot,按提示创建机器人", @@ -189,9 +198,29 @@ "botFeishuStep1Prefix": "前往", "botFeishuOpenPlatform": "飞书开放平台", "botFeishuStep1Suffix": ",创建企业自建应用", - "botFeishuStep2": "启用机器人能力,配置事件订阅", + "botFeishuStep2": "启用机器人能力,配置权限管理和事件与回调", "botFeishuStep3": "将App ID和App Secret填入下方", - "openNgrokSetup": "打开 ngrok 安装与配置页面" + "openNgrokSetup": "打开 ngrok 安装与配置页面", + "disclaimerTitle": "远程连接免责声明", + "disclaimerIntro": "启用远程连接前,请确认你已理解并接受以下事项:", + "disclaimerItemBeta": "远程连接目前为 Beta 版本,可能存在未发现的安全漏洞、功能缺陷或不兼容变更,请在充分了解风险后使用。", + "disclaimerItemSecurity": "远程连接会开启与网络通信相关的能力(包括但不限于局域网、第三方中继、自建服务、机器人通道等),请仅在可信网络和可信设备上使用。", + "disclaimerItemEncryption": "远程连接采用端到端加密(X25519 ECDH + AES-256-GCM,每次会话生成临时密钥对)传输业务消息,中继服务器无法解密消息内容;但设备名称、连接状态、服务地址等必要元数据及其他连接上下文信息不属于业务消息密文范畴,仍可能被网络路径、服务节点或其他基础设施感知。", + "disclaimerItemOpenSource": "BitFun 远程连接的加密实现完全开源,你可以自行审计源码以验证安全性。", + "disclaimerItemPrivacy": "请勿在不受信任环境中传输敏感信息;机器人消息平台、第三方网络、系统日志策略等可能影响你的数据暴露面,其他外部服务策略变化亦可能带来额外风险。", + "disclaimerItemDataUsage": "BitFun 不会将你的远程连接业务内容用于训练或商业化使用;但你选择接入的第三方服务(如 ngrok、Telegram、飞书、自建服务器运维组件等)可能按其条款处理数据,其他关联服务亦可能产生数据处理行为。", + "disclaimerItemCredentials": "使用 ngrok、自建中继、Telegram/飞书机器人等方式时,相关地址、Token、App Secret、密钥及其他访问凭证由你自行保管,泄露可能导致未授权访问或其他安全事件。", + "disclaimerItemQrCode": "二维码中包含中继地址、房间标识、公钥等连接信息及其他会话标识,请避免将其截图、录屏或展示给无关人员。", + "disclaimerItemNgrok": "使用 ngrok 或其他第三方隧道服务时,流量会经过外部服务链路。请自行评估账号安全、地域合规、配额限制、服务可用性与潜在中间链路风险等因素。", + "disclaimerItemSelfHosted": "使用自建中继服务时,你需自行负责主机与容器安全(包括但不限于系统补丁、访问控制、TLS 证书、端口暴露、防火墙、DDoS/爆破防护、日志审计、备份恢复等)。服务器被攻击、配置错误、密钥泄露或其他运维失误可能导致连接中断、数据泄露或被篡改风险。", + "disclaimerItemNetwork": "网络环境、防火墙、路由器隔离策略(如 AP/客户端隔离)、代理/隧道服务设置等以及其他网络策略都可能影响连接结果与稳定性。", + "disclaimerItemBot": "机器人模式下发送的配对码仅用于当前会话绑定,请勿向无关人员泄露;其他机器人平台机制或策略变更也可能影响安全性与可用性。", + "disclaimerItemBotPersistence": "机器人配对信息会保存在本地磁盘以便重启后自动恢复连接;如果本机被未授权人员访问、恶意软件入侵或发生其他系统安全事件,该信息可能被利用。", + "disclaimerItemMobileBrowser": "移动端通过浏览器加载 Web 应用进行连接,浏览器缓存、本地存储、页面截图、系统剪贴板等都可能带来额外的信息暴露风险,其他浏览器插件或系统功能亦可能扩大风险面。", + "disclaimerItemCompliance": "部分国家或地区对加密通信、隧道服务、跨境数据传输等有法规限制,请自行评估当地法律合规性;其他监管政策变化也可能影响功能可用性与合规义务。", + "disclaimerItemLiability": "使用远程连接功能过程中产生的风险、损失、纠纷或其他不利后果(包括但不限于数据泄露、连接中断、账号异常、服务不可用等)由你自行承担;BitFun 不对由此造成的问题负责。", + "disclaimerDecline": "不同意", + "disclaimerAgree": "同意并继续" }, "about": { "close": "关闭",