From de795de0fb1815da7819f5a2a1b0a97990cbe823 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 11 Sep 2026 11:49:31 +0800 Subject: [PATCH] feat(install): show the sync token on the install page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 插件必须填 NAVI_SYNC_TOKEN,但页面上没有任何地方能看到它 —— 用户只能去翻 .env,实际就是配不起来。 - admin-server 新增 GET /admin/api/sync-info,返回 syncToken / hasToken。 放在 /admin/ 下而不是 /api/ 下是必须的:插件上报用的 /api/history/sync 被门户 auth_request 放行,若令牌挂在同一前缀下就等于公开。 实测 /api/history/sync-info 返回 401,只有 /admin/api/sync-info 能取到。 - 安装引导页把「第一步」改为填写两项(服务地址 + 同步令牌),各自可复制; 服务端未设令牌时提示留空即可;未登录时提示需先登录门户。 - README 补充对应说明。 --- admin-server/main.go | 20 ++++++++++++ web/extension/install.html | 65 ++++++++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/admin-server/main.go b/admin-server/main.go index ae8a9e1..e445c40 100644 --- a/admin-server/main.go +++ b/admin-server/main.go @@ -27,6 +27,8 @@ var ( // 所以默认**不校验**;只有当你把容器端口直接暴露给不受信的网段、 // 而没有外层认证时才设 NAVI_ADMIN_REQUIRE_TOKEN=1。 requireTokenForWrite = isTruthy(os.Getenv("NAVI_ADMIN_REQUIRE_TOKEN")) + // 浏览器插件同步用的令牌(供安装引导页展示,方便用户拷贝) + syncToken = os.Getenv("NAVI_SYNC_TOKEN") // 本地上传的图标目录(Dashy 通过 /item-icons/ 读取) iconDir = envOr("NAVI_ICON_DIR", filepath.Join(filepath.Dir(confFile), "item-icons")) // 壁纸目录(nginx 通过 /wallpapers/ 提供) @@ -456,6 +458,23 @@ func handleDeleteItem(w http.ResponseWriter, r *http.Request) { jsonResp(w, map[string]string{"status": "ok"}) } +// handleSyncInfo 把浏览器插件需要的接入信息给已登录用户。 +// +// 为何可以在这里返回令牌:/admin/ 整体在门户登录墙之内; +// 而 /api/history/sync 是被门户放行的(否则插件无法上报), +// 所以这个 token 仍然是有意义的凭据,并且不能挂在 /api/ 下暴露。 +func handleSyncInfo(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + jsonErr(w, 405, "method not allowed") + return + } + jsonResp(w, map[string]any{ + "syncToken": syncToken, + "hasToken": syncToken != "", + "requireAdminToken": requireTokenForWrite, + }) +} + // ── 站点信息:标题 / 副标题 / 顶部导航 / 页脚 ── // 这些东西原先只能在 conf.yml 里手改,现在可在 /admin/ 编辑。 func handlePageInfo(w http.ResponseWriter, r *http.Request) { @@ -628,6 +647,7 @@ func main() { http.HandleFunc("/api/pageinfo", cors(handlePageInfo)) http.HandleFunc("/api/icon", cors(handleIconUpload)) http.HandleFunc("/api/wallpaper", cors(handleWallpaper)) + http.HandleFunc("/api/sync-info", cors(handleSyncInfo)) http.HandleFunc("/api/reload", cors(func(w http.ResponseWriter, r *http.Request) { jsonResp(w, map[string]string{"status": "saved"}) })) diff --git a/web/extension/install.html b/web/extension/install.html index 667c586..6ce4ad1 100644 --- a/web/extension/install.html +++ b/web/extension/install.html @@ -42,6 +42,7 @@ .site input{flex:1;min-width:240px;background:#0b1220;border:1px solid #334155;border-radius:10px; padding:9px 12px;color:#e2e8f0;font-size:14px} .hint{font-size:13px;color:#94a3b8;margin-top:10px} + .lbl{display:block;font-size:12px;font-weight:600;color:#94a3b8;margin:12px 0 0} .done{font-size:13px;color:#86efac;margin-top:8px} @@ -51,12 +52,22 @@

安装后浏览记录会自动同步到本站,并据此把最常访问的服务排到「常用服务」最前面。

-

第一步:填写本站地址

-

在插件设置里粘贴下面这个地址(点「复制」)。同步令牌从部署方的 NAVI_SYNC_TOKEN 环境变量获取。

+

第一步:填写插件设置

+

在插件设置里填下面两项,各自点「复制」再粘贴。

+ +
+ + +
+ + +
+

+
@@ -127,18 +138,54 @@ document.getElementById('site-url').value = location.origin; - document.getElementById('copy-btn').addEventListener('click', function () { - var input = document.getElementById('site-url'); + var doneTimer = null; + function flashDone() { + var done = document.getElementById('copy-done'); + done.hidden = false; + if (doneTimer) clearTimeout(doneTimer); + doneTimer = setTimeout(function () { done.hidden = true; }, 2000); + } + + function copyFrom(inputId) { + var input = document.getElementById(inputId); + if (!input || !input.value) return; input.select(); input.setSelectionRange(0, 999); - var done = document.getElementById('copy-done'); - function ok() { done.hidden = false; setTimeout(function () { done.hidden = true; }, 2000); } if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(input.value).then(ok, function () { document.execCommand('copy'); ok(); }); + navigator.clipboard.writeText(input.value).then(flashDone, function () { + document.execCommand('copy'); flashDone(); + }); } else { - document.execCommand('copy'); ok(); + document.execCommand('copy'); flashDone(); } - }); + } + + document.getElementById('copy-btn').addEventListener('click', function () { copyFrom('site-url'); }); + document.getElementById('copy-token').addEventListener('click', function () { copyFrom('site-token'); }); + + /* 同步令牌:由 admin-server 提供(/admin/ 在门户登录墙之内, + 所以令牌不会出现在被门户放行的 /api/ 路径上)。 */ + fetch('/admin/api/sync-info', { cache: 'no-store' }) + .then(function (r) { return r.ok ? r.json() : null; }) + .then(function (d) { + var tk = document.getElementById('site-token'); + var note = document.getElementById('token-note'); + if (d && d.hasToken) { + tk.value = d.syncToken; + note.textContent = '把这两项填进插件设置即可。'; + } else if (d) { + tk.value = ''; + tk.placeholder = '(服务端未设置令牌)'; + document.getElementById('copy-token').hidden = true; + note.textContent = '服务端未配置 NAVI_SYNC_TOKEN,令牌留空即可。'; + } else { + tk.placeholder = '无法读取,请确认已登录'; + note.textContent = '需要登录门户后才能读取令牌。'; + } + }) + .catch(function () { + document.getElementById('site-token').placeholder = '无法读取,请确认已登录'; + }); /* ── 浏览器识别 ── UA 会互相包含(Edge/Brave/Opera 的 UA 里都有 Chrome/),所以要按