cdrop — 跨 OS 剪贴板与文件传输服务
Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。 - 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。 - 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。 - 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。 - 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。 架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。 本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
|
||||
"cdrop-desktop/platform"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
// Create an instance of the app structure
|
||||
app := NewApp()
|
||||
|
||||
// Reverse-proxy /api/* to the backend so the embedded web UI can keep its
|
||||
// same-origin relative URLs (see platform.NewAPIProxy). SSE streams through.
|
||||
apiProxy, err := platform.NewAPIProxy(app.apiBase)
|
||||
if err != nil {
|
||||
println("Error:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Windows: Wails' custom-scheme AssetServer buffers a handler's response until
|
||||
// it returns, so the long-lived SSE never reaches the WebView (device list
|
||||
// never loads, status stuck on "connecting"). Serve /api from a loopback HTTP
|
||||
// origin instead — WebView2's normal network stack streams it — and point the
|
||||
// page there via the injected api_base. macOS streams the custom scheme fine,
|
||||
// so it keeps api_base="" (same-origin relative /api through apiProxy).
|
||||
webAPIBase := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
if base, lerr := platform.StartLocalAPIServer(app.apiBase); lerr == nil {
|
||||
webAPIBase = base
|
||||
} else {
|
||||
println("local api server:", lerr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the backend this is a native desktop client, not a browser, so the
|
||||
// device list shows the right kind. The page forwards it as X-Device-Type.
|
||||
deviceType := ""
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
deviceType = "macos"
|
||||
case "windows":
|
||||
deviceType = "windows"
|
||||
case "linux":
|
||||
deviceType = "linux"
|
||||
}
|
||||
|
||||
// Restore the persisted login + device name by injecting them into index.html
|
||||
// at load — the wails:// scheme has no persistent WebView storage (see
|
||||
// session.go).
|
||||
session, _ := platform.LoadSession()
|
||||
|
||||
// When the OS starts us at login, the autostart entry appends a --hidden flag
|
||||
// (see SetLaunchAtLogin). Come up straight to the menu bar / tray with no
|
||||
// window — the user didn't open the app, so don't steal focus. The frontend
|
||||
// still loads, so clipboard sync runs in the background. A manual launch has
|
||||
// no flag and shows the window as usual.
|
||||
hidden := launchedHidden()
|
||||
app.startHidden = hidden
|
||||
|
||||
// Create application with options
|
||||
err = wails.Run(&options.App{
|
||||
Title: "cdrop",
|
||||
Width: 1024,
|
||||
Height: 768,
|
||||
StartHidden: hidden,
|
||||
AssetServer: &assetserver.Options{
|
||||
Assets: assets,
|
||||
Handler: apiProxy,
|
||||
Middleware: platform.SessionInjectMiddleware(session, platform.ResolveDeviceName(), webAPIBase, deviceType),
|
||||
},
|
||||
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||
Menu: app.buildMenu(),
|
||||
OnStartup: app.startup,
|
||||
OnBeforeClose: app.beforeClose,
|
||||
Bind: []interface{}{
|
||||
app,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
println("Error:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// launchedHidden reports whether this process was started by the login autostart
|
||||
// entry, which appends a --hidden flag to the launch command (see
|
||||
// SetLaunchAtLogin on each platform). Manual launches never carry it.
|
||||
func launchedHidden() bool {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if arg == "--hidden" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user