diff --git a/src/apps/desktop/src/api/browser_api.rs b/src/apps/desktop/src/api/browser_api.rs index b204f7f78a..d631665a83 100644 --- a/src/apps/desktop/src/api/browser_api.rs +++ b/src/apps/desktop/src/api/browser_api.rs @@ -20,7 +20,35 @@ pub async fn browser_webview_eval( app: tauri::AppHandle, request: WebviewEvalRequest, ) -> Result<(), String> { - Err("Webview not found".to_string()) + #[cfg(not(target_env = "ohos"))] + { + let webview = app + .get_webview(&request.label) + .ok_or_else(|| format!("Webview not found: {}", request.label))?; + + webview + .eval(&request.script) + .map_err(|e| format!("eval failed: {e}")) + } + #[cfg(target_env = "ohos")] + { + use bitfun_core::util::JS_THREADSAFE_FUNCTION; + use napi_ohos::threadsafe_function::ThreadsafeFunctionCallMode; + use serde_json; + + let _ = app; + let function = { + let lock = JS_THREADSAFE_FUNCTION.read(); + lock.get("browser_webview_eval").cloned() + }; + let Some(function) = function else { + return Err("The Arkts has not register the function browser_webview_eval".to_owned()); + }; + let payload = serde_json::to_string(&request) + .map_err(|e| format!("Failed to serialize request: {e}"))?; + function.call(Ok(payload), ThreadsafeFunctionCallMode::NonBlocking); + Ok(()) + } } #[derive(Debug, Deserialize)] @@ -39,5 +67,39 @@ pub async fn browser_get_url( app: tauri::AppHandle, request: WebviewLabelRequest, ) -> Result { - Err("Url is unavailable (webview URL is nil)".to_string()) + #[cfg(not(target_env = "ohos"))] + { + let webview = app + .get_webview(&request.label) + .ok_or_else(|| format!("Webview not found: {}", request.label))?; + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| webview.url())); + + match result { + Ok(Ok(url)) => Ok(url.to_string()), + Ok(Err(e)) => Err(format!("url failed: {e}")), + Err(_) => Err("url unavailable (webview URL is nil)".to_string()), + } + } + #[cfg(target_env = "ohos")] + { + use bitfun_core::util::JS_THREADSAFE_FUNCTION; + + let _ = app; + let function = { + let lock = JS_THREADSAFE_FUNCTION.read(); + lock.get("browser_get_url").cloned() + }; + let Some(function) = function else { + return Err("The Arkts has not register the function browser_get_url".to_owned()); + }; + let res = function.call_async(Ok(request.label)).await; + match res { + Ok(promise) => match promise.await { + Ok(url) => Ok(url), + Err(err) => Err(err.to_string()), + }, + Err(err) => Err(err.to_string()), + } + } } diff --git a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets index 5595518f52..3fcbedee8f 100644 --- a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets +++ b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -18,6 +18,7 @@ import { BusinessError } from '@kit.BasicServicesKit'; import RustModule from 'libbitfun_desktop_lib.so'; import { CommonUtils } from '../utils/CommonUtils'; import { runDeveco } from '../utils/DevecoStart'; +import { webViewManager } from '../utils/WebViewManager'; const DOMAIN = 0x0000; @@ -188,6 +189,26 @@ export default class EntryAbility extends RustAbility { } return ''; }); + RustModule.registerArktsFunction('browser_webview_eval', async (err: Error, payload: string): Promise => { + interface WebviewEvalRequest { + label: string; + script: string; + } + try { + const request: WebviewEvalRequest = JSON.parse(payload); + return await webViewManager.evalJavaScript(request.label, request.script); + } catch (parseError) { + hilog.error(DOMAIN, 'vnext', `browser_webview_eval parse error: ${JSON.stringify(parseError)}`); + return `Failed to parse request: ${JSON.stringify(parseError)}`; + } + }); + RustModule.registerArktsFunction('browser_get_url', async (err: Error, label: string): Promise => { + const url = webViewManager.getUrl(label); + if (!url) { + return 'Url is unavailable (webview URL is nil)'; + } + return url; + }); return super.onWindowStageCreate(windowStage); } diff --git a/src/apps/ohos/entry/src/main/ets/pages/EmbeddedBrowserPage.ets b/src/apps/ohos/entry/src/main/ets/pages/EmbeddedBrowserPage.ets new file mode 100644 index 0000000000..46d558ac8c --- /dev/null +++ b/src/apps/ohos/entry/src/main/ets/pages/EmbeddedBrowserPage.ets @@ -0,0 +1,45 @@ +import { webview } from '@kit.ArkWeb'; +import { webViewManager } from '../utils/WebViewManager'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const DOMAIN = 0x0000; +const TAG = 'EmbeddedBrowserPage'; + +@Entry +@Component +struct EmbeddedBrowserPage { + @State url: string = 'https://openbitfun.com/'; + @State label: string = 'embedded-browser-view-0'; + controller: webview.WebviewController = new webview.WebviewController(); + + aboutToAppear(): void { + webViewManager.registerController(this.label, this.controller); + hilog.info(DOMAIN, TAG, `EmbeddedBrowserPage aboutToAppear, registered controller: ${this.label}`); + } + + aboutToDisappear(): void { + webViewManager.unregisterController(this.label); + hilog.info(DOMAIN, TAG, `EmbeddedBrowserPage aboutToDisappear, unregistered controller: ${this.label}`); + } + + build() { + Column() { + Web({ src: this.url, controller: this.controller }) + .width('100%') + .height('100%') + .javaScriptAccess(true) + .domStorageAccess(true) + .onPageBegin((event) => { + hilog.info(DOMAIN, TAG, `Page begin: ${event?.url}`); + }) + .onPageEnd((event) => { + hilog.info(DOMAIN, TAG, `Page end: ${event?.url}`); + }) + .onErrorReceive((event) => { + hilog.error(DOMAIN, TAG, `Page error: ${event?.error.getErrorInfo()}`); + }) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/src/apps/ohos/entry/src/main/ets/utils/WebViewManager.ets b/src/apps/ohos/entry/src/main/ets/utils/WebViewManager.ets new file mode 100644 index 0000000000..361a1fadd4 --- /dev/null +++ b/src/apps/ohos/entry/src/main/ets/utils/WebViewManager.ets @@ -0,0 +1,67 @@ +import { webview } from '@kit.ArkWeb'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const DOMAIN = 0x0000; +const TAG = 'WebViewManager'; + +export class WebViewManager { + private static instance: WebViewManager; + private controllers: Map = new Map(); + + static getInstance(): WebViewManager { + if (!WebViewManager.instance) { + WebViewManager.instance = new WebViewManager(); + } + return WebViewManager.instance; + } + + registerController(label: string, controller: webview.WebviewController): void { + this.controllers.set(label, controller); + hilog.info(DOMAIN, TAG, `Registered webview controller: ${label}`); + } + + unregisterController(label: string): void { + this.controllers.delete(label); + hilog.info(DOMAIN, TAG, `Unregistered webview controller: ${label}`); + } + + getController(label: string): webview.WebviewController | null { + return this.controllers.get(label) ?? null; + } + + async evalJavaScript(label: string, script: string): Promise { + const controller = this.getController(label); + if (!controller) { + hilog.error(DOMAIN, TAG, `Webview not found: ${label}`); + return `Webview not found: ${label}`; + } + + try { + const result = await controller.runJavaScript(script); + hilog.info(DOMAIN, TAG, `evalJavaScript success: ${script}`); + return ''; + } catch (error) { + hilog.error(DOMAIN, TAG, `evalJavaScript failed: ${JSON.stringify(error)}`); + return `eval failed: ${JSON.stringify(error)}`; + } + } + + getUrl(label: string): string { + const controller = this.getController(label); + if (!controller) { + hilog.error(DOMAIN, TAG, `Webview not found: ${label}`); + return ''; + } + + try { + const url = controller.getUrl(); + hilog.info(DOMAIN, TAG, `getUrl success: ${url}`); + return url; + } catch (error) { + hilog.error(DOMAIN, TAG, `getUrl failed: ${JSON.stringify(error)}`); + return ''; + } + } +} + +export const webViewManager = WebViewManager.getInstance(); \ No newline at end of file diff --git a/src/web-ui/package.json b/src/web-ui/package.json index eaec6ccf42..4d5d1df67d 100644 --- a/src/web-ui/package.json +++ b/src/web-ui/package.json @@ -21,7 +21,6 @@ "@monaco-editor/react": "^4.6.0", "@tanstack/react-virtual": "^3.13.23", "@tauri-apps/api": "^2.10.1", - "@tauri-apps/plugin-autostart": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.6.0", "@tauri-apps/plugin-fs": "^2.0.0", "@tauri-apps/plugin-log": "^2.8.0", diff --git a/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts b/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts index 41d7d8bc0e..c0b2d73421 100644 --- a/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts +++ b/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts @@ -3,7 +3,6 @@ import { api } from './ApiClient'; import { createTauriCommandError } from '../errors/TauriCommandError'; import { openUrl } from '@tauri-apps/plugin-opener'; -import { disable as autostartDisable, enable as autostartEnable, isEnabled as autostartIsEnabled } from '@tauri-apps/plugin-autostart'; import { createLogger } from '@/shared/utils/logger'; @@ -172,19 +171,6 @@ export class SystemAPI { } } - /** Desktop only: whether the app is registered to launch at OS login. */ - async getLaunchAtLoginEnabled(): Promise { - if (typeof window === 'undefined' || !('__TAURI__' in window)) { - return false; - } - try { - return await autostartIsEnabled(); - } catch (error) { - log.error('Failed to read launch-at-login state', error); - throw createTauriCommandError('autostart_is_enabled', error); - } - } - /** Desktop only: send an OS-level desktop notification. */ async sendSystemNotification(title: string, body?: string): Promise { if (typeof window === 'undefined' || !('__TAURI__' in window)) { @@ -199,23 +185,6 @@ export class SystemAPI { } } - /** Desktop only: register or unregister launch at OS login. */ - async setLaunchAtLoginEnabled(enabled: boolean): Promise { - if (typeof window === 'undefined' || !('__TAURI__' in window)) { - return; - } - try { - if (enabled) { - await autostartEnable(); - } else { - await autostartDisable(); - } - } catch (error) { - log.error('Failed to set launch-at-login', { enabled, error }); - throw createTauriCommandError('autostart_set', error, { enabled }); - } - } - // ─── Window / Tray behavior ──────────────────────────────────────────────── /** Desktop only: immediately quit the application. */ diff --git a/src/web-ui/src/infrastructure/config/components/BasicsConfig.tsx b/src/web-ui/src/infrastructure/config/components/BasicsConfig.tsx index 73aac14764..5e9a55e15e 100644 --- a/src/web-ui/src/infrastructure/config/components/BasicsConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/BasicsConfig.tsx @@ -11,7 +11,6 @@ import { ConfigPageMessage, } from '@/component-library'; import { configAPI, workspaceAPI } from '@/infrastructure/api'; -import { systemAPI } from '@/infrastructure/api/service-api/SystemAPI'; import type { CloseBehavior } from '@/infrastructure/api/service-api/SystemAPI'; import { getTerminalService, @@ -38,103 +37,6 @@ import './BasicsConfig.scss'; const log = createLogger('BasicsConfig'); -function BasicsLaunchAtLoginSection() { - const { t } = useTranslation('settings/basics'); - const isTauri = typeof window !== 'undefined' && '__TAURI__' in window; - const [enabled, setEnabled] = useState(false); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [message, setMessage] = useState<{ type: 'success' | 'error' | 'info'; text: string } | null>(null); - - const showMessage = useCallback((type: 'success' | 'error' | 'info', text: string) => { - setMessage({ type, text }); - setTimeout(() => setMessage(null), 3000); - }, []); - - useEffect(() => { - if (!isTauri) { - setLoading(false); - return; - } - - let cancelled = false; - void (async () => { - try { - setLoading(true); - const v = await systemAPI.getLaunchAtLoginEnabled(); - if (!cancelled) { - setEnabled(v); - } - } catch (error) { - log.error('Failed to load launch-at-login state', error); - if (!cancelled) { - showMessage('error', t('launchAtLogin.messages.loadFailed')); - } - } finally { - if (!cancelled) { - setLoading(false); - } - } - })(); - - return () => { - cancelled = true; - }; - }, [isTauri, showMessage, t]); - - const handleToggle = useCallback( - async (next: boolean) => { - const previous = enabled; - setEnabled(next); - setSaving(true); - try { - await systemAPI.setLaunchAtLoginEnabled(next); - } catch (error) { - setEnabled(previous); - log.error('Failed to set launch-at-login', { next, error }); - showMessage('error', t('launchAtLogin.messages.saveFailed')); - } finally { - setSaving(false); - } - }, - [enabled, showMessage, t] - ); - - if (!isTauri) { - return null; - } - - if (loading) { - return ; - } - - return ( -
-
- - - - { - void handleToggle(e.target.checked); - }} - disabled={saving} - /> - - -
-
- ); -} - function BasicsAutoUpdateSection() { const { t } = useTranslation('settings/basics'); const isTauri = typeof window !== 'undefined' && '__TAURI__' in window; @@ -813,7 +715,6 @@ const BasicsConfig: React.FC = () => { - diff --git a/src/web-ui/src/locales/en-US/settings/basics.json b/src/web-ui/src/locales/en-US/settings/basics.json index ff962479a5..2dae0332bc 100644 --- a/src/web-ui/src/locales/en-US/settings/basics.json +++ b/src/web-ui/src/locales/en-US/settings/basics.json @@ -87,19 +87,6 @@ "saved": "Update preference saved" } }, - "launchAtLogin": { - "sections": { - "title": "Login", - "hint": "Start BitFun when you sign in to this account" - }, - "toggleLabel": "Launch at login", - "toggleDescription": "Automatically start BitFun after you log in. Exact behavior depends on your operating system.", - "messages": { - "loading": "Loading...", - "loadFailed": "Failed to read launch-at-login setting", - "saveFailed": "Failed to update launch-at-login setting" - } - }, "logging": { "sections": { "logging": "Logging", diff --git a/src/web-ui/src/locales/zh-CN/settings/basics.json b/src/web-ui/src/locales/zh-CN/settings/basics.json index b0e1d937f9..6d0fe7c4eb 100644 --- a/src/web-ui/src/locales/zh-CN/settings/basics.json +++ b/src/web-ui/src/locales/zh-CN/settings/basics.json @@ -87,19 +87,6 @@ "saved": "已保存更新偏好" } }, - "launchAtLogin": { - "sections": { - "title": "登录时启动", - "hint": "登录本用户后是否自动运行 BitFun" - }, - "toggleLabel": "开机自动启动", - "toggleDescription": "登录系统后自动启动 BitFun;具体表现因操作系统而异。", - "messages": { - "loading": "加载中…", - "loadFailed": "无法读取开机启动设置", - "saveFailed": "无法更新开机启动设置" - } - }, "logging": { "sections": { "logging": "日志", diff --git a/src/web-ui/src/locales/zh-TW/settings/basics.json b/src/web-ui/src/locales/zh-TW/settings/basics.json index 12af5df84e..7aec1194d9 100644 --- a/src/web-ui/src/locales/zh-TW/settings/basics.json +++ b/src/web-ui/src/locales/zh-TW/settings/basics.json @@ -73,19 +73,6 @@ "flowChatPxOption": "{{n}} px" } }, - "launchAtLogin": { - "sections": { - "title": "登錄時啟動", - "hint": "登錄本用戶後是否自動運行 BitFun" - }, - "toggleLabel": "開機自動啟動", - "toggleDescription": "登錄系統後自動啟動 BitFun;具體表現因操作系統而異。", - "messages": { - "loading": "載入中…", - "loadFailed": "無法讀取開機啟動設置", - "saveFailed": "無法更新開機啟動設置" - } - }, "logging": { "sections": { "logging": "日誌",