feat(更新提醒): 原生更新入口接入
build.yaml / build (push) Successful in 47s

This commit is contained in:
xiaoqidun committed 2026-10-23 22:21:03 +08:00
1 parent 68c58ef8c9
commit 7682a7dd5d
2 files changed
+51 -6

No files matched your search

+13 -5
View File
@@ -5053,6 +5053,7 @@ async function boot() {
applyTitleBarInfo(await nativeHost.call("window.info", {})); applyTitleBarInfo(await nativeHost.call("window.info", {}));
new ResizeObserver(updateTitleBarRegions).observe(el.documentTitleBar); new ResizeObserver(updateTitleBarRegions).observe(el.documentTitleBar);
await nativeHost.start(openNativeFiles, error => showError(error, !state.doc)); await nativeHost.start(openNativeFiles, error => showError(error, !state.doc));
watchApplicationUpdates();
} }
try { try {
const remote = remoteDocumentOptions(window.location.hash); const remote = remoteDocumentOptions(window.location.hash);
@@ -5140,18 +5141,20 @@ function watchApplicationUpdates(checksum) {
if (force === true) retry = true; if (force === true) retry = true;
return; return;
} }
if (document.hidden || !navigator.onLine) { if (document.hidden || !nativeHost && !navigator.onLine) {
return; return;
} }
checking = true; checking = true;
try { try {
if (!checksum) { if (!nativeHost && !checksum) {
if (!navigator.serviceWorker.controller) return; if (!navigator.serviceWorker.controller) return;
checksum = await applicationChecksum("./ofdgo.js"); checksum = await applicationChecksum("./ofdgo.js");
if (!checksum) return; if (!checksum) return;
} }
const latest = await applicationChecksum("./", { method: "HEAD", cache: "no-store" }); const latest = nativeHost ? await nativeHost.call("update.check", {}) : await applicationChecksum("./", { method: "HEAD", cache: "no-store" });
if (latest) { if (nativeHost) {
el.refreshAppButton.hidden = !latest;
} else if (latest) {
el.refreshAppButton.hidden = latest === checksum; el.refreshAppButton.hidden = latest === checksum;
} }
} catch { } catch {
@@ -5240,7 +5243,12 @@ function requestOffline(worker, type) {
} }
async function refreshApplication() { async function refreshApplication() {
if (nativeHost || state.exporting || document.body.hasAttribute("data-busy")) { if (nativeHost) {
try { await nativeHost.call("update.open", {}); }
catch (err) { showError(err, false); }
return;
}
if (state.exporting || document.body.hasAttribute("data-busy")) {
return; return;
} }
if (workspace.tabs.some(tab => tab.state.saving)) { if (workspace.tabs.some(tab => tab.state.saving)) {
+38 -1
View File
@@ -33,6 +33,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/xiaoqidun/guigo" "github.com/xiaoqidun/guigo"
"github.com/xiaoqidun/ofdgo" "github.com/xiaoqidun/ofdgo"
@@ -44,6 +45,9 @@ import (
// applicationID 标识原生应用,与版本及可执行文件路径无关 // applicationID 标识原生应用,与版本及可执行文件路径无关
const applicationID = "me.aite.ofdgo" const applicationID = "me.aite.ofdgo"
const applicationVersionURL = "https://ofdgo.aite.me/"
const applicationDownloadURL = "https://ofdgo.aite.me/releases/"
// Host 连接桌面系统能力和共用文档业务 // Host 连接桌面系统能力和共用文档业务
type Host struct { type Host struct {
mu sync.Mutex mu sync.Mutex
@@ -102,7 +106,7 @@ func Run(ctx context.Context, paths []string, dataDir string) error {
h.conversionPool = h.resources.ConversionPool() h.conversionPool = h.resources.ConversionPool()
defer h.close() defer h.close()
bindings := make(map[string]guigo.HandlerFunc) bindings := make(map[string]guigo.HandlerFunc)
for _, name := range []string{"engine.open", "engine.close", "engine.call", "engine.start", "engine.cancel", "engine.finish", "batch", "open", "save", "directory", "file", "release", "drop.release", "child", "remove", "write.begin", "write.data", "write.commit", "write.abort", "fonts", "ready", "title", "window.info", "window.drag", "window.close"} { for _, name := range []string{"engine.open", "engine.close", "engine.call", "engine.start", "engine.cancel", "engine.finish", "batch", "open", "save", "directory", "file", "release", "drop.release", "child", "remove", "write.begin", "write.data", "write.commit", "write.abort", "fonts", "ready", "title", "window.info", "window.drag", "window.close", "update.check", "update.open"} {
bindings["ofdgo.host."+name] = func(ctx context.Context, args any) (any, error) { return h.call(ctx, name, args) } bindings["ofdgo.host."+name] = func(ctx context.Context, args any) (any, error) { return h.call(ctx, name, args) }
} }
static := http.FileServerFS(webuiassets.NativeFS) static := http.FileServerFS(webuiassets.NativeFS)
@@ -326,6 +330,10 @@ func (h *Host) call(ctx context.Context, name string, value any) (any, error) {
return guigo.Result{Value: map[string]any{"engine": id, "parallelism": parallelism, "cacheBytes": cacheBytes}, Discard: func() { _ = h.closeEngine(id) }}, nil return guigo.Result{Value: map[string]any{"engine": id, "parallelism": parallelism, "cacheBytes": cacheBytes}, Discard: func() { _ = h.closeEngine(id) }}, nil
case "batch": case "batch":
return map[string]any{"parallelism": h.conversionPool.Parallelism()}, nil return map[string]any{"parallelism": h.conversionPool.Parallelism()}, nil
case "update.check":
return applicationUpdate(ctx, build.Version, applicationVersionURL)
case "update.open":
return nil, guigo.OpenURL(applicationDownloadURL)
case "engine.close": case "engine.close":
return nil, h.closeEngine(text("engine")) return nil, h.closeEngine(text("engine"))
case "engine.finish": case "engine.finish":
@@ -588,6 +596,35 @@ func (h *Host) call(ctx context.Context, name string, value any) (any, error) {
return nil, fmt.Errorf("未知宿主操作") return nil, fmt.Errorf("未知宿主操作")
} }
// applicationUpdate 比较官网和当前程序的完整构建版本
// 入参: ctx 请求上下文, version 当前版本, endpoint 官网地址
// 返回: bool 是否有更新, error 检查错误
func applicationUpdate(ctx context.Context, version, endpoint string) (bool, error) {
if version == "" {
return false, nil
}
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodHead, endpoint, nil)
if err != nil {
return false, err
}
request.Header.Set("Cache-Control", "no-store")
response, err := http.DefaultClient.Do(request)
if err != nil {
return false, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return false, fmt.Errorf("检查更新失败: %s", response.Status)
}
latest := response.Header.Get("X-OFDGo-Build-Version")
if latest == "" {
return false, errors.New("未提供构建版")
}
return latest != version, nil
}
// dialogFilters 将共用界面的文件类型映射到系统对话框 // dialogFilters 将共用界面的文件类型映射到系统对话框
// 入参: args 对话框参数 // 入参: args 对话框参数
// 返回: []guigo.FileFilter 文件类型列表 // 返回: []guigo.FileFilter 文件类型列表