From 7d6a7c2321648e6e3ba1dd5f4da60924cb2847b2 Mon Sep 17 00:00:00 2001
From: t8y2 <1156263951@qq.com>
Date: Fri, 21 Aug 2026 14:55:20 +0800
Subject: [PATCH] feat(desktop): support dbx://open deep links
---
docs/content/docs/cli.cn.mdx | 18 +++++++++++++++--
docs/content/docs/cli.mdx | 18 +++++++++++++++--
src-tauri/src/commands/deep_link.rs | 31 ++++++++++++++++++++++-------
src-tauri/src/lib.rs | 4 ++++
4 files changed, 60 insertions(+), 11 deletions(-)
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())