diff --git a/docs/content/docs/cli.cn.mdx b/docs/content/docs/cli.cn.mdx index a59e21b15e..4fca3a1fc6 100644 --- a/docs/content/docs/cli.cn.mdx +++ b/docs/content/docs/cli.cn.mdx @@ -168,7 +168,21 @@ CLI JSON 错误使用稳定错误码: ## 桌面端 Deep Link -DBX 桌面端支持通过 `dbx://connection/new` 从浏览器、堡垒机或脚本打开新建连接窗口并预填连接信息。 +DBX 桌面端支持通过 Deep Link 从浏览器、堡垒机或脚本唤起客户端。 + +仅启动或聚焦 DBX,不打开其他窗口: + +```bash +open 'dbx://open' +``` + +网页按钮可以直接使用: + +```html +使用 DBX 打开 +``` + +使用 `dbx://connection/new` 可以打开新建连接窗口并预填连接信息。 DSN 模式: @@ -214,7 +228,7 @@ open 'dbx://connection/new?type=r-nacos&url=http%3A%2F%2F127.0.0.1%3A8848%2Fnaco `nacos` 是 `nacos-v2` 的别名,`rnacos` 是 `r-nacos` 的别名。URL 模式会保留服务端点路径,顶层 `host`、`port`、`user`、`password`、`ssl` 会覆盖 `url` 中的对应值。Consul 的 `password` 表示 ACL token;Nacos 传入 `password` 时必须同时传入 `user`。省略 `v` 或使用 `v=1` 表示当前深链协议,其他版本会被拒绝。 -测试系统级 `dbx://` 唤起前,请先安装并启动一次 DBX 桌面端,让系统注册 URL 协议。macOS 可使用 `open 'dbx://connection/new?...'` 测试。 +测试系统级 `dbx://` 唤起前,请先安装并启动一次 DBX 桌面端,让系统注册 URL 协议。macOS 可使用 `open 'dbx://open'` 或 `open 'dbx://connection/new?...'` 测试。 深链可能在浏览器或终端历史中暴露数据库密码或 Consul ACL token。不要将携带凭据的链接写入共享日志、Shell 历史、工单或聊天消息。 diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 2ab3bc5186..d80e83d68d 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -170,7 +170,21 @@ CLI JSON errors use stable codes: ## Desktop Deep Links -DBX Desktop supports `dbx://connection/new` links for opening the new connection dialog from browsers, bastion hosts, or scripts with connection fields prefilled. +DBX Desktop supports deep links for launching the client from browsers, bastion hosts, or scripts. + +Launch or focus DBX without opening another dialog: + +```bash +open 'dbx://open' +``` + +A web button can link to the protocol directly: + +```html +Open in DBX +``` + +Use `dbx://connection/new` to open the new connection dialog with connection fields prefilled. DSN mode: @@ -216,7 +230,7 @@ open 'dbx://connection/new?type=r-nacos&url=http%3A%2F%2F127.0.0.1%3A8848%2Fnaco `nacos` is an alias for `nacos-v2`, and `rnacos` is an alias for `r-nacos`. In URL mode, service endpoint paths are preserved; top-level `host`, `port`, `user`, `password`, and `ssl` fields override values from `url`. For Consul, `password` is the ACL token. Nacos requires `user` when `password` is present. Omitting `v` or using `v=1` selects the current deep-link protocol; other versions are rejected. -Before testing system-level `dbx://` launches, install and open DBX Desktop once so the operating system can register the URL scheme. On macOS, use `open 'dbx://connection/new?...'` to test it. +Before testing system-level `dbx://` launches, install and open DBX Desktop once so the operating system can register the URL scheme. On macOS, use `open 'dbx://open'` or `open 'dbx://connection/new?...'` to test it. A deep link may expose a database password or Consul ACL token in browser and terminal history. Do not put credential-bearing links in shared logs, shell history, tickets, or chat messages. diff --git a/src-tauri/src/commands/deep_link.rs b/src-tauri/src/commands/deep_link.rs index a42cfa6488..32787c9b2f 100644 --- a/src-tauri/src/commands/deep_link.rs +++ b/src-tauri/src/commands/deep_link.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::sync::Mutex; const CONNECTION_DEEP_LINK_PREFIX: &str = "dbx://connection/new"; +const APP_OPEN_DEEP_LINK_PREFIX: &str = "dbx://open"; #[tauri::command] pub fn pending_open_connection_links(state: tauri::State<'_, DeepLinkOpenState>) -> Vec { @@ -38,17 +39,23 @@ where pub fn connection_deep_link_from_arg(arg: &str) -> Option { let trimmed = arg.trim(); - let suffix = trimmed.strip_prefix(CONNECTION_DEEP_LINK_PREFIX)?; - if !(suffix.is_empty() + matches_deep_link_target(trimmed, CONNECTION_DEEP_LINK_PREFIX).then(|| trimmed.to_string()) +} + +pub fn is_app_open_deep_link(arg: &str) -> bool { + matches_deep_link_target(arg.trim(), APP_OPEN_DEEP_LINK_PREFIX) +} + +fn matches_deep_link_target(value: &str, target: &str) -> bool { + let Some(suffix) = value.strip_prefix(target) else { + return false; + }; + suffix.is_empty() || suffix.starts_with('?') || suffix.starts_with('#') || suffix == "/" || suffix.starts_with("/?") - || suffix.starts_with("/#")) - { - return None; - } - Some(trimmed.to_string()) + || suffix.starts_with("/#") } fn dedupe_links(links: Vec) -> Vec { @@ -79,6 +86,16 @@ mod tests { assert_eq!(links, vec!["dbx://connection/new?type=mysql&host=127.0.0.1".to_string()]); } + #[test] + fn recognizes_app_open_deep_links() { + assert!(is_app_open_deep_link("dbx://open")); + assert!(is_app_open_deep_link(" dbx://open?source=sponsor ")); + assert!(is_app_open_deep_link("dbx://open/#landing")); + assert!(!is_app_open_deep_link("dbx://opened")); + assert!(!is_app_open_deep_link("dbx://open/window")); + assert!(!is_app_open_deep_link("dbx://connection/new")); + } + #[test] fn drains_pending_links_once() { let state = DeepLinkOpenState::default(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a676f494df..9c3d11707c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2348,6 +2348,10 @@ pub fn run() { #[cfg(target_os = "macos")] if let RunEvent::Opened { urls } = &event { + if urls.iter().any(|url| commands::deep_link::is_app_open_deep_link(url.as_str())) { + show_main_window(app_handle); + } + let links: Vec = urls .iter() .map(|url| url.to_string())