feat: 统一 Commilitia Drop 全客户端命名与分发

This commit is contained in:
2026-07-31 22:56:10 +08:00
parent f7b0f04c9c
commit c480f0d1c2
77 changed files with 861 additions and 263 deletions
+4 -4
View File
@@ -1,4 +1,4 @@
# cdrop 桌面原生数据面(Option A)设计
# Commilitia Drop 桌面原生数据面(Option A)设计
> 目标:把桌面 **P2P 数据面**WebRTC DataChannel 收发)从 WebView 的 JS 移到 Go 进程(`pion/webrtc`),**留在 Wails、不上 Tauri**。
> 关联:`desktop/PLAN.md`(桌面总计划)、`desktop/RESEARCH.md`(调研)、`web/src/features/transfer/{transfer,p2p,relay,incomingSink,source}.ts`、吞吐专项诊断(task #38)。
@@ -62,7 +62,7 @@ selectBackend() → isDesktop() ? goBridgeBackend : jsWebrtcBackend // web / i
```
Sender(sessionId, peerName, filePath, iceServers):
pc ← pion.NewPeerConnection(SettingEngine{ rwnd 大; host 候选真 IP; iceServers })
dc ← pc.CreateDataChannel("cdrop-file", ordered=true)
dc ← pc.CreateDataChannel("commilitia-drop-file", ordered=true)
onOpen:
send JSON {type:meta, name, size, sha256?} // 与 p2p.ts 同帧
for chunk in readFileByPath(filePath, CHUNK): // Go 直接磁盘读,无桥、无 base64
@@ -117,7 +117,7 @@ ICE credsGo 拉 /api/calls/credentials(带 a.token),与 JS 同源
> 双实现的唯一契约是**线协议**。任何一项漂移都会让桌面↔web/iOS 互通断裂。建议抽出共享测试向量(meta/chunk/done/ack 的字节级样例)双侧回归。
- **信令**`POST /api/hub/signal {to, payload:{type:"offer"|"answer"|"ice", sdp?, candidate?}}`;入站经 SSE。
- **DataChannel**:名 `"cdrop-file"``ordered:true`
- **DataChannel**:名 `"commilitia-drop-file"``ordered:true`
- **帧**`meta` JSON `{type,name,size,sha256?}` / 二进制 chunk / `done` JSON / `ack` JSON `{type,bytes}`(接收端节流 200ms 回传累计已收字节,单调取大)。
- **状态机**`/api/transfer/{initiate,p2p,done,fail,cancel,fallback}`**完成由接收端 POST `/done`**(权威),发送端不重复。
- **中继回退语义**:JS 看门狗 30s 未 `connected``POST /fallback` 并对 Go `Cancel(session)`Go 拆 pionrelay 由 JS 接管(数据面回到 JS relay,符合「relay 留 JS」)。
@@ -130,7 +130,7 @@ ICE credsGo 拉 /api/calls/credentials(带 a.token),与 JS 同源
>
> 实测证据:iOS 经 gomobile/pion 时 Go 的 raw BSD socket 不与 iOS Network framework 集成(WebKit/libwebrtc 才集成)——Mac→iOS 常连不上(~30s ICE 超时)、iOS→Mac 偶连上 host↔host 但 ~4.5MB 中途断流(`read/write on closed pipe`)、TURN CreatePermission 刷屏、时好时坏。接口 / 链路本地 / IPv4-only 过滤只去噪、治不了架构不兼容(且那些 iOS 定向过滤漏进桌面共享引擎致 IPv6 host 对被裁退中继、重大回归)。即下方 §137 标注的“LOW-MEDIUM 成熟度、无 pion+gomobile 生产先例”风险兑现。
>
> **U2 落地(现行)**:JS 路由 / 桥协议 / 线协议 / `IOS_NATIVE` 开关全不变,只把 Swift 侧原生引擎从 gomobile `EngineEngine` 换成 `ios/CDrop/Sources/Engine/LibWebRtcEngine.swift`libwebrtc `RTCPeerConnection`stasel/WebRTC M149 经 SPM、pin 精确版本 149.0.0)。该引擎是 `engine.go`/`session.go` 线协议的忠实 Swift 端口——DataChannel `cdrop-file`ordered)、meta/分片(64KB)/done/ack 文本+二进制帧、16MB/4MB 水位背压、ack 追平完成、不冲突落盘,逐字节对齐桌面 pion 与 web JS 引擎,故 iOS↔桌面、iOS↔浏览器互通。桌面数据面仍走 pion(isDesktop 分支,不受影响)。libwebrtc 与 iOS 网络栈原生集成 + DcSCTP 默认 ~5MB rwnd(不受 WebKit 256KB 限),是吞吐与连通性的根治。
> **U2 落地(现行)**:JS 路由 / 桥协议 / 线协议 / `IOS_NATIVE` 开关全不变,只把 Swift 侧原生引擎从 gomobile `EngineEngine` 换成 `ios/CDrop/Sources/Engine/LibWebRtcEngine.swift`libwebrtc `RTCPeerConnection`stasel/WebRTC M149 经 SPM、pin 精确版本 149.0.0)。该引擎是 `engine.go`/`session.go` 线协议的忠实 Swift 端口——DataChannel `commilitia-drop-file`ordered)、meta/分片(64KB)/done/ack 文本+二进制帧、16MB/4MB 水位背压、ack 追平完成、不冲突落盘,逐字节对齐桌面 pion 与 web JS 引擎,故 iOS↔桌面、iOS↔浏览器互通。桌面数据面仍走 pion(isDesktop 分支,不受影响)。libwebrtc 与 iOS 网络栈原生集成 + DcSCTP 默认 ~5MB rwnd(不受 WebKit 256KB 限),是吞吐与连通性的根治。
>
> **验证(2026-06-28**:模拟器全 app(含 Share/控件扩展)编译过 + web typecheck/build 过;**环回端到端测试**(两个 LibWebRtcEngine 同进程交叉连、20MB 文件经完整线协议传输后逐字节完整性比对)✅ 过(`ios/CDrop/Tests/LibWebRtcEngineTests.swift``just ios-sim-build` 同款免签名构建)。真机 / 真网吞吐为剩余闸(须 deploy prod 让设备拿到 `IOS_NATIVE=true` 的引擎 + 真机装机,二者 Touch ID 门控)。
>
+22 -10
View File
@@ -1,4 +1,4 @@
# cdrop 桌面客户端实施计划
# Commilitia Drop 桌面客户端实施计划
> Wails v2 · macOS 优先 + Windows 已落地
> 状态:**macOS 与 Windows 双端均已实现并 RDP/真机实测通过**——A2 前端复用 + 托盘/菜单栏 + 剪贴板双向同步 + 桌面设置 + session 持久化 + 开机自启(含自启静默驻留)+ 设备类型登记。macOS 见 §10Windows 落地(含「custom-scheme 不支持流式」核心坑与 127.0.0.1 本地代理解法)见 §11。计划已据原生平台研究 sweep`desktop/RESEARCH.md`)校正。
@@ -26,7 +26,9 @@
## 0. 范围、非范围与本轮关键决策
**范围**:常驻桌面客户端,补浏览器做不到的——后台常驻、剪贴板自动监听上传、文件落盘、系统通知、全局快捷键。平台 **macOSApple Silicon + Intel)优先****Windows(x64)保持兼容**(同源、同分支,平台差异用 Go build tag 隔离)。
**范围**:常驻桌面客户端,补浏览器做不到的——后台常驻、剪贴板自动监听上传、文件落盘、
系统通知、全局快捷键。平台 **macOS(仅 Apple Silicon)优先****Windowsx64)保持兼容**
(同源、同分支,平台差异用 Go build tag 隔离)。
**非范围(推后)**Linux 打包、iOS / Android 原生、自更新(仅版本检查提示)、产品化 / 多租户、Admin、Symmetric NAT 的 mDNS 直连、本地内容缓存 / 离线。
@@ -109,7 +111,7 @@ sweep 把 D1 从“最高风险未知”变成“路径已明、含一处后端
### 3.1 回调通道:loopback(已定,弃 scheme
采用 **RFC 8252 loopback**Go 起临时 `127.0.0.1:0`(必须 IPv4 字面量,不用 `localhost`HTTP server 捕获 `code`。相对自定义 scheme `cdrop://` 的优势:免单实例锁、免 `Info.plist` / 注册表登记、免深链解析、跨平台代码一致。scheme 留作未来“浏览器点 cdrop 链接唤起桌面端”的另一用例。
采用 **RFC 8252 loopback**Go 起临时 `127.0.0.1:0`(必须 IPv4 字面量,不用 `localhost`HTTP server 捕获 `code`。相对自定义 scheme `commilitia-drop://` 的优势:免单实例锁、免 `Info.plist` / 注册表登记、免深链解析、跨平台代码一致。scheme 留作未来“浏览器点 Commilitia Drop 链接唤起桌面端”的另一用例。
### 3.2 token 交换放 Go 侧(已定)
@@ -166,13 +168,20 @@ sweep 源码级确认:Casdoor 支持 public clientPKCE 时 `client_secret`
- **Quick Send 浮窗(sweep 校正)****Wails v2 单原生窗口**,开不了第二个原生窗。v2 内只能“同窗 HTML 浮层 / 路由切换”近似;“失焦自隐”v2 无现成钩子、要自写 ObjC `NSWindowDelegate windowDidResignKey`(v2 路线最脏一块)。**这是上 v3 的最强理由**(v3 多窗口每窗一等对象)。
- 产品化时建议热键可重绑(规避撞车)。
### D6 · 打包(无证书:仅 `.app`
- **macOS(当前路径)**`wails build -platform darwin/universal` 产出 `build/bin/<App>.app``lipo` 合并 arm64 + amd64)。**不公证、不 DMG、无 Developer ID**。为满足 D3 通知与稳定身份,做 **ad-hoc 签名** `codesign -s - --force --deep <App>.app` + `Info.plist``CFBundleIdentifier`。分发靠手动(右键打开 / 去 quarantine)。
- **entitlements(若 ad-hoc 也带)**:非沙箱只需 `com.apple.security.network.client`purego dlopen 系统 framework **不需** `disable-library-validation`Carbon 热键不需 entitlement。(不要列通知 / 剪贴板 / 热键 entitlement——过度声明。)
### D6 · 打包与分发(2026-07-31 已验证
- **macOS(当前路径)**`just desktop-dist-mac` 构建 Apple Silicon ARM64 Wails app,复用
既有 Developer ID Application 证书,以 hardened runtime + secure timestamp 签名;先提交
app 公证并 staple,再生成含 `/Applications` 链接的 DMG,对 DMG 签名、单独公证并 staple。
app 与 DMG 均已获 Apple `Accepted`Gatekeeper 评估为 `Notarized Developer ID`
- **entitlements**:当前非沙箱瘦客户端不额外声明通知、剪贴板或热键 entitlementpurego
`dlopen` Apple 系统 framework 不需 `disable-library-validation`Carbon 热键也不需要
entitlement。后续引入第三方 dylib 或沙箱时重新评估。
- **Windows**`wails build -platform windows/amd64 [-nsis]` 产出 `.exe` / NSIS 安装器;当前**不签名**SmartScreen 会提示),获代码签名证书后再 `signtool`
- **版本检查**:启动 / 定时拉版本端点,过期提示去下载,**不自更新**。
- **将来获证书**:完整 codesignDeveloper ID+ notarytool + DMG + CI 流程见 `desktop/RESEARCH.md` D6 手册,届时直接套用。
- **CI**D6 后 GitHub Actions 双 runnermacos-latest / windows-latest)跑构建 + 防回归(无证书阶段仅构建 + ad-hoc,不跑公证)
- **操作入口**:完整构建、签名、公证、安装和验收流程见
`docs/client-build-install.md`;底层命令与设计依据见 `desktop/RESEARCH.md` D6 手册
- **CI**:后续 GitHub Actions 双 runnermacos-latest / windows-latest)跑构建 + 防回归;
macOS runner 导入 Developer ID 后跑公证,Windows 在获得 Authenticode 证书后加入签名。
---
@@ -296,7 +305,8 @@ A2 复用(地基,先让真 web UI 在桌面跑起来 + 登录走 Go 桥)
### 10.6 实现状态(2026-06-13 实现,本地全绿)
`go build ./... / go test -race ./... / web tsc / wails build` 全部通过,产出 `build/bin/cdrop-desktop.app`(内嵌真 web 应用,8 个绑定就绪)。
`go build ./... / go test -race ./... / web tsc / wails build` 全部通过,产出
`build/bin/Commilitia Drop.app`(内嵌真 web 应用,8 个绑定就绪)。
落地与对 §10.1–10.4 的关键决策:
@@ -389,7 +399,9 @@ Windows 侧无 cgo——darwin 的 NSStatusBar/NSPasteboard CGO 走 build tag
- **设备类型**:客户端发 `X-Device-Type`(桌面按 `runtime.GOOS` 注入 macos/windows),后端中间件白名单登记(替代写死 `browser`),前端 i18n 本地化展示(浏览器 / macOS 客户端 / Windows 客户端…)。
- **设备名持久化**修复:`SaveSettings` 合并保留 `DeviceName`(设置页 `DesktopConfig` 无 device_name 字段,原先存设置会用空值覆盖)。
- **统一软件名** `Commilitia Drop Desktop``.app` / `.exe`);品牌 `.icns`macOS/ `.ico`Windows,无 ImageMagick 时用一次性 Go 工具封 PNG-in-ICO);bundle id `net.commilitia.cdrop`
- **统一软件名** `Commilitia Drop``.app` / `.exe`);品牌 `.icns`macOS/ `.ico`
Windows,无 ImageMagick 时用一次性 Go 工具封 PNG-in-ICO);稳定内部 bundle id
`net.commilitia.cdrop`
- **Cmd+,Ctrl+,)→ 设置**`main.tsx` 全局 keydown,桌面与浏览器通用。
### 11.5 自启静默驻留(autostart → 不弹窗,直接驻留菜单栏 / 托盘)
+22 -12
View File
@@ -1,19 +1,29 @@
# README
# Commilitia DropWails
## About
这是 Windows 与 macOS 当前唯一的正式桌面客户端。两个平台都复用仓库根目录 `web/`
的 React 前端;`frontend/src` 只保留为早期 Wails 桥接示例,不是可发布的第二套界面。
This is the official Wails Vanilla-TS template.
正式构建必须从仓库根目录运行:
You can configure the project by editing `wails.json`. More information about the project settings can be found
here: https://wails.io/docs/reference/project-config
```sh
just desktop-build-win
just desktop-build-mac
```
## Live Development
macOS 正式分发使用既有 Developer ID Application 证书和 ASC API Key
To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.
```sh
just desktop-dist-mac
```
## Building
该任务会依次签名、公证并装订 `.app`,再生成、签名、公证和装订
`desktop/build/bin/Commilitia Drop.dmg`。Windows 当前输出未做 Authenticode 签名。
To build a redistributable, production mode package, use `wails build`.
macOS 构建仅支持 Apple Silicon(ARM64)。构建任务会先生成最新 Web 产物,再以
`wails build -clean` 打包,避免将陈旧前端或另一平台的旧产物混入发布目录。
`ios/CDrop` 中的原生 macOS target 仍是迁移候选,只供本地验证;在功能对等、三语界面、
签名扩展和真机验收全部完成前,不得代替或与本 Wails 客户端同时分发。
完整的全客户端构建、安装和验证步骤见
[`../docs/client-build-install.md`](../docs/client-build-install.md)。
+45 -20
View File
@@ -1,6 +1,8 @@
# cdrop 桌面端 D1 前决策就绪简报
# Commilitia Drop 桌面端 D1 前决策就绪简报
> 框架基线:Wails v2.12.0Go 1.23 模块声明,本机 toolchain 1.26.3+ 系统 WebView瘦客户端,业务全走 `drop.commilitia.net`。平台优先级 macOSApple Silicon + Intel universal),Windows x64 兼容。
> 框架基线:Wails v2.12.0Go 1.23 模块声明,本机 toolchain 1.26.3+ 系统 WebView
> 瘦客户端,业务全走 `drop.commilitia.net`。平台优先级 macOS(仅 Apple Silicon),
> Windows x64 兼容。
> 已对四项关键论断做对抗式核验:托盘能力 **refuted**、热键库可用性 **supported(附 pin 条件)**、Casdoor/R1 **mixed(需一处后端改动 + 版本前提)**、签名/公证 entitlement **mixed(核心子句成立,捆绑表述需修正)**。
---
@@ -121,7 +123,7 @@
本节只覆盖 Wails/Go 客户端侧的实现机制。前提已确定:public client + PKCE,回调用 `http://127.0.0.1:<临时端口>/callback`Casdoor 对 loopback 任意端口放行,后端 audience 改多值校验。Casdoor 端点:authorize=`/login/oauth/authorize`token=`/api/login/oauth/access_token`
### 1. loopback vs 自定义 scheme `cdrop://` 的取舍(Wails 语境)
### 1. loopback vs 自定义 scheme `commilitia-drop://` 的取舍(Wails 语境)
两条路线都符合 RFC 8252 对原生应用的要求(§7.1 私有 scheme、§7.3 loopback),但在 Wails 下实现成本差异明显。
@@ -132,10 +134,15 @@
- 跨平台零差异:macOS / Windows / Linux 代码一致,只依赖 `net/http`
- 必须用 IP 字面量 `127.0.0.1`(或 `[::1]`),不要用 `localhost`——RFC 8252 §8.3:用 `localhost` 可能因 DNS / hosts 解析意外监听到非回环接口。端口用 OS 分配的临时端口(`:0`),符合 RFC 8252 §7.3“服务器 MUST 允许请求时指定任意端口”。
**自定义 scheme`cdrop://callback`):**
**自定义 scheme`commilitia-drop://callback`):**
- 需要 OS 级登记:macOS 在 `Info.plist``CFBundleURLTypes``CFBundleURLSchemes`(值 `cdrop`);Windows 要在注册表 `HKEY_CLASSES_ROOT\cdrop`(或 per-user `HKCU\Software\Classes\cdrop`)写 `URL Protocol` 键 + `shell\open\command` 指向 exeLinux 走 `.desktop``x-scheme-handler/cdrop`
- 必须配单实例锁。点 `cdrop://` 时 OS 会重新拉起 app 的“第二个实例”,深链作为命令行参数传入。需要 `options.App.SingleInstanceLock` 把这个 deep link 转发给首实例,否则 token 落在一个马上要退出的临时进程里,拿不到。
- 需要 OS 级登记:macOS 在 `Info.plist``CFBundleURLTypes``CFBundleURLSchemes`
(值 `commilitia-drop`);Windows 要在注册表
`HKEY_CLASSES_ROOT\commilitia-drop`(或 per-user
`HKCU\Software\Classes\commilitia-drop`)写 `URL Protocol` 键 +
`shell\open\command` 指向 exeLinux 走
`.desktop``x-scheme-handler/commilitia-drop`
- 必须配单实例锁。点 `commilitia-drop://` 时 OS 会重新拉起 app 的“第二个实例”,深链作为命令行参数传入。需要 `options.App.SingleInstanceLock` 把这个 deep link 转发给首实例,否则 token 落在一个马上要退出的临时进程里,拿不到。
- macOS 上单实例锁与深链协作有已知坑(见 wails issue #5089v3 的 single instance lock 与 `OpenedWithURL` 不兼容;v2 也需自己从 `Args` 解析 URL 并 `WindowUnminimise` + `Show`)。
scheme 路线的 `SingleInstanceLock` 形态(作为对比,**本项目不采用**):
@@ -145,9 +152,9 @@ scheme 路线的 `SingleInstanceLock` 形态(作为对比,**本项目不采
SingleInstanceLock: &options.SingleInstanceLock{
UniqueId: "net.commilitia.cdrop",
OnSecondInstanceLaunch: func(d options.SecondInstanceData) {
// d.Args 里含被 OS 透传的 cdrop://callback?code=...&state=...
// d.Args 里含被 OS 透传的 commilitia-drop://callback?code=...&state=...
for _, arg := range d.Args {
if strings.HasPrefix(arg, "cdrop://") {
if strings.HasPrefix(arg, "commilitia-drop://") {
runtime.WindowUnminimise(appCtx) // 回调不会自动聚焦窗口
runtime.Show(appCtx)
runtime.EventsEmit(appCtx, "oauth:callback", arg)
@@ -157,7 +164,7 @@ SingleInstanceLock: &options.SingleInstanceLock{
},
```
**推荐:loopback。** 在 Wails 下它省掉了单实例锁、`Info.plist`/注册表登记、深链解析与跨平台分叉这一整套,授权流在单进程内自洽,代码量和真机调试面都小得多。`cdrop://` 的唯一优势是不占端口、回调 URL 更“原生”,但对瘦客户端不值这些成本。scheme 留作未来若需“浏览器里点 cdrop 链接唤起桌面端”的备选——那是另一个用例(深链唤起),与本次登录回调无关。
**推荐:loopback。** 在 Wails 下它省掉了单实例锁、`Info.plist`/注册表登记、深链解析与跨平台分叉这一整套,授权流在单进程内自洽,代码量和真机调试面都小得多。`commilitia-drop://` 的唯一优势是不占端口、回调 URL 更“原生”,但对瘦客户端不值这些成本。scheme 留作未来若需“浏览器里点 Commilitia Drop 链接唤起桌面端”的备选——那是另一个用例(深链唤起),与本次登录回调无关。
### 2. `runtime.BrowserOpenURL` 用法与注意
@@ -397,13 +404,13 @@ export async function onLoginClick(): Promise<void>
func writeClosePage(w http.ResponseWriter, ok bool)
{
w.Header().Set("Content-Type", "text/html; charset=utf-8")
msg := "已登录,可关闭本页返回 cdrop。"
msg := "已登录,可关闭本页返回 Commilitia Drop。"
if !ok
{
msg = "登录未完成,可关闭本页返回 cdrop 重试。"
msg = "登录未完成,可关闭本页返回 Commilitia Drop 重试。"
}
fmt.Fprintf(w, `<!doctype html><html lang="zh-Hans"><head><meta charset="utf-8">`+
`<title>cdrop</title></head><body style="font-family:sans-serif;text-align:center;margin-top:20vh">`+
`<title>Commilitia Drop</title></head><body style="font-family:sans-serif;text-align:center;margin-top:20vh">`+
`<p>%s</p><script>setTimeout(function(){window.close();},800);</script>`+
`</body></html>`, msg)
}
@@ -443,7 +450,8 @@ func writeClosePage(w http.ResponseWriter, ok bool)
## 打包·签名·公证操作手册(D6 补充)
本节只补**具体命令与 CI 配置**;entitlement/公证“会不会被拒”的判断已在前文完成。环境基准:Wails v2.12.0Go),macOS universal 优先 + Windows x64,瘦客户端。
本节只补**具体命令与 CI 配置**;entitlement/公证“会不会被拒”的判断已在前文完成。
环境基准:Wails v2.12.0Go),macOS arm64 + Windows x64,瘦客户端。
> 重要前提:Wails 官方 signing 指南至今仍推荐 `gon`,但该工具已停止维护、且依赖已废弃的 `altool` 语义。本手册一律改用 Apple 现行的 `codesign` + `notarytool` + `stapler` 链路,这是已确定的正确路径。
@@ -452,13 +460,15 @@ func writeClosePage(w http.ResponseWriter, ok bool)
#### 1.1 构建产物与已知坑(已确定)
```bash
wails build -platform darwin/universal -clean
wails build -platform darwin/arm64 -clean
```
- 产物:`build/bin/<AppName>.app`universal `.app` bundle`lipo` 已合并 arm64 + amd64 两份 Go 二进制)。`<AppName>` 来自 `wails.json``outputfilename`/项目名。
- 产物:`build/bin/<AppName>.app`Apple Silicon arm64)。`<AppName>` 来自
`wails.json``outputfilename`/项目名。
- 项目脚手架在 `build/darwin/` 下生成 `Info.plist`(模板 `Info.plist``Info.dev.plist`),这是 `.app``Contents/Info.plist` 的来源。改 bundle ID、版本号、`LSMinimumSystemVersion` 等都改这里。
- 已知坑:
- universal 构建要求**本机同时具备 arm64 与 amd64 的 CGO 工具链**。在 Apple Silicon 的 `macos-latest`macos-14/15runner 上原生满足;不要尝试从 Linux 交叉编译 darwinCGO + macOS SDK 缺失,社区反复确认不可行)。
- 当前只构建 arm64;不要尝试从 Linux 交叉编译 darwinCGO + macOS SDK 缺失,
社区反复确认不可行)。
- `wails build` 本身**不签名、不公证 macOS 产物**(与 Windows 的 `-nsis` 不同,没有内建签名参数)。签名/公证完全是构建后的独立步骤。
- Wails 没有内建 DMG 封装,需自己做(见 1.4)。
@@ -563,7 +573,22 @@ xcrun stapler staple "YourApp.dmg"
#### 1.6 Wails 对 darwin 签名的内建支持(已确定)
`wails build -platform darwin/universal` **无签名参数**,不做 codesignnotarize;签名、公证、DMG 全是构建后的外部步骤。`build/darwin/` 下有 `Info.plist``Info.dev.plist``entitlements.plist` 非自动生成,需手动放进该路径。universal 由 Wails 内部 `lipo` 合并,无需手动 `lipo`
`wails build -platform darwin/arm64` **无签名参数**,不做 codesignnotarize;签名、
公证、DMG 全是构建后的外部步骤。`build/darwin/` 下有 `Info.plist``Info.dev.plist`
`entitlements.plist` 非自动生成,需手动放进该路径。
#### 1.7 本项目已验证的正式 recipe2026-07-31
仓库根目录的 `just desktop-dist-mac` 已把上述外部步骤固化:ARM64 构建 → 复用既有
Developer ID Application 身份签名 → `ditto` ZIP 公证 → app staple → 生成 DMG → DMG 签名与
单独公证 → DMG staple → Gatekeeper 评估。DMG 由 `desktop/scripts/make-dmg.sh` 使用纯系统工具
创建,内容只有 `Commilitia Drop.app``/Applications` 链接,不依赖 `create-dmg`
本次实测 app submission `0e2c80e1-e40d-4108-951e-64c810bd118e`、DMG submission
`7a6aea7a-6c40-4bdb-96e3-46e6f8fbc813` 均为 `Accepted`;两者 staplevalidate 成功,
`spctl` 返回 `Notarized Developer ID`。这证明 Wails 应用本身满足 Apple 公证要求;后续发布应
复用 recipe,不再使用 ad-hoc 签名或未公证 `.app` 作为正式分发物。面向操作者的完整流程见
`docs/client-build-install.md`
### 2. Windows
@@ -596,7 +621,7 @@ signtool sign /fd sha256 /tr http://ts.ssl.com /td sha256 /f certificate.pfx /p
```yaml
jobs:
macos:
runs-on: macos-latest # Apple Silicon,原生支持 universal CGO
runs-on: macos-latest # Apple Silicon arm64
steps:
- uses: actions/checkout@v4
with: { submodules: recursive } # cjk-autospace 等 submodule
@@ -605,7 +630,7 @@ jobs:
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0
- run: wails build -platform darwin/universal -clean
- run: wails build -platform darwin/arm64 -clean
- uses: apple-actions/import-codesign-certs@v7
with:
p12-file-base64: ${{ secrets.APPLE_CERT_P12_BASE64 }}
@@ -685,7 +710,7 @@ jobs:
- Wails Mac App Store 指南(entitlements/codesign --options=runtime、build/darwin 路径):https://wails.io/docs/guides/mac-appstore/
- Wails NSIS installer 指南(build/windows/installer、-nsis):https://wails.io/docs/guides/windows-installer/
- Wails NSIS 不签主 exe 的 issue #3716https://github.com/wailsapp/wails/issues/3716
- Wails Crossplatform buildGitHub Actions、darwin/universal 矩阵):https://wails.io/docs/guides/crossplatform-build/
- Wails Crossplatform buildGitHub Actions):https://wails.io/docs/guides/crossplatform-build/
- notarytool man pagehttps://keith.github.io/xcode-man-pages/notarytool.1.html
- Apple TN3147 迁移到新公证工具:https://developer.apple.com/documentation/technotes/tn3147-migrating-to-the-latest-notarization-tool
- Apple Disable Library Validation entitlementhttps://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation
+8 -5
View File
@@ -62,6 +62,9 @@ func (a *App) startup(ctx context.Context) {
if platform.IsLaunchAtLoginEnabled() {
_ = platform.SetLaunchAtLogin(true)
}
// Migrate native notification metadata at startup so an old internal-name
// entry does not remain visible until the first incoming notification.
platform.InitializeNotifications()
// 原生传输引擎:落地目录取当前配置(设置页改目录时经 SaveSettings 同步到引擎)。
a.transfer = engine.New(engine.Config{DownloadDir: platform.ResolveDownloadDir()}, &transferEvents{app: a})
a.startClipboardSync(ctx)
@@ -70,10 +73,10 @@ func (a *App) startup(ctx context.Context) {
platform.TriggerLocalNetwork()
platform.InstallStatusBar(
platform.StatusBarMenu{
Title: "cdrop",
Title: "Commilitia Drop",
Show: "显示主窗口",
Settings: "设置…",
Quit: "退出 cdrop",
Quit: "退出 Commilitia Drop",
},
// The native menu-action callbacks fire on the AppKit main thread; calling
// Wails runtime methods synchronously there can re-enter the main run loop
@@ -107,10 +110,10 @@ func (a *App) buildMenu() *menu.Menu {
m := menu.NewMenu()
appSub := menu.NewMenu()
appSub.Append(menu.Text("退出 cdrop", keys.CmdOrCtrl("q"), func(*menu.CallbackData) {
appSub.Append(menu.Text("退出 Commilitia Drop", keys.CmdOrCtrl("q"), func(*menu.CallbackData) {
a.requestQuit()
}))
m.Append(menu.SubMenu("cdrop", appSub)) // macOS renders the first menu as the app menu
m.Append(menu.SubMenu("Commilitia Drop", appSub)) // macOS renders the first menu as the app menu
m.Append(menu.EditMenu()) // Undo/Redo/Cut/Copy/Paste/SelectAll
@@ -415,7 +418,7 @@ func (a *App) resolveOAuthConfig() (platform.OAuthConfig, error) {
func oauthConfigFromEnv() platform.OAuthConfig {
return platform.OAuthConfig{
BrokerURL: os.Getenv("CDROP_BROKER_URL"),
App: envOr("CDROP_BROKER_APP", "cdrop"),
App: envOr("CDROP_BROKER_APP", "commilitia-drop"),
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
<string>Commilitia Drop 使用本地网络发现同内网设备并建立点对点直连传输。</string>
<key>NSBonjourServices</key>
<array>
<string>_cdrop._tcp</string>
<string>_commilitia-drop._tcp</string>
</array>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
+1 -1
View File
@@ -29,7 +29,7 @@
<string>Commilitia Drop 使用本地网络发现同内网设备并建立点对点直连传输。</string>
<key>NSBonjourServices</key>
<array>
<string>_cdrop._tcp</string>
<string>_commilitia-drop._tcp</string>
</array>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
+2 -2
View File
@@ -7,7 +7,7 @@
// 留给宿主(桌面 JS / iOS Swift),引擎只经回调收发不透明信令串——纯数据面。
//
// 线协议与 web 引擎 web/src/features/transfer/p2p.ts 逐字节一致,故 Go 端可与浏览器 /
// iOS 的 JS 引擎互通:DataChannel "cdrop-file"ordered),控制帧 meta/done/ack 走文本帧、
// iOS 的 JS 引擎互通:DataChannel "commilitia-drop-file"ordered),控制帧 meta/done/ack 走文本帧、
// 文件分片走二进制帧;信令 payload 形如 {type, sdp?, candidate?}。
package engine
@@ -22,7 +22,7 @@ import (
)
const (
channelName = "cdrop-file"
channelName = "commilitia-drop-file"
chunkSize = 64 * 1024
highWatermark = 16 * 1024 * 1024
lowWatermark = 4 * 1024 * 1024
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>cdrop-desktop</title>
<title>Commilitia Drop</title>
</head>
<body>
<div id="app"></div>
+2 -2
View File
@@ -8,9 +8,9 @@ import { EventsOn } from "../wailsjs/runtime/runtime";
// Go 换 token → oauth:success / oauth:error 事件回到这里。
document.querySelector("#app")!.innerHTML = `
<main class="login-demo">
<h1>cdrop 桌面端</h1>
<h1>Commilitia Drop</h1>
<p class="status" id="status">检查登录状态……</p>
<button class="btn" id="loginBtn" disabled>登录 cdrop</button>
<button class="btn" id="loginBtn" disabled>登录 Commilitia Drop</button>
<p class="hint">登录会在系统浏览器中打开授权页,完成后自动返回。</p>
</main>
`;
+1 -1
View File
@@ -67,7 +67,7 @@ func main() {
// Create application with options
err = wails.Run(&options.App{
Title: "cdrop",
Title: "Commilitia Drop",
Width: 1024,
Height: 768,
StartHidden: hidden,
+8 -1
View File
@@ -68,7 +68,14 @@ func ResolveDeviceName() string {
if h, err := os.Hostname(); err == nil && h != "" {
return h
}
return "cdrop-desktop"
switch runtime.GOOS {
case "darwin":
return "Commilitia Drop (macOS)"
case "windows":
return "Commilitia Drop (Windows)"
default:
return "Commilitia Drop"
}
}
// DefaultConfig is what a fresh install gets: clipboard sync on, no autostart.
+1 -1
View File
@@ -43,7 +43,7 @@ func FetchOAuthConfig(ctx context.Context, apiBase string) (OAuthConfig, error)
app := c.App
if app == "" {
app = "cdrop"
app = "commilitia-drop"
}
cfg := OAuthConfig{BrokerURL: c.BrokerURL, App: app}
if cfg.BrokerURL == "" {
+6 -6
View File
@@ -17,7 +17,7 @@ func TestFetchOAuthConfig_Success(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{
"auth_mode": "prod",
"broker_url": "https://sso.example.net",
"broker_app": "cdrop",
"broker_app": "commilitia-drop",
})
}))
defer srv.Close()
@@ -30,13 +30,13 @@ func TestFetchOAuthConfig_Success(t *testing.T) {
if cfg.BrokerURL != "https://sso.example.net" {
t.Errorf("broker_url = %q", cfg.BrokerURL)
}
if cfg.App != "cdrop" {
t.Errorf("app = %q, want cdrop", cfg.App)
if cfg.App != "commilitia-drop" {
t.Errorf("app = %q, want commilitia-drop", cfg.App)
}
}
func TestFetchOAuthConfig_DefaultsApp(t *testing.T) {
// broker_app omitted → defaults to "cdrop".
// broker_app omitted → defaults to "commilitia-drop".
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"broker_url": "https://sso.example.net"})
@@ -46,8 +46,8 @@ func TestFetchOAuthConfig_DefaultsApp(t *testing.T) {
if err != nil {
t.Fatalf("FetchOAuthConfig: %v", err)
}
if cfg.App != "cdrop" {
t.Errorf("app = %q, want default cdrop", cfg.App)
if cfg.App != "commilitia-drop" {
t.Errorf("app = %q, want default commilitia-drop", cfg.App)
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
)
// 代铸 (proxy-mint) on the desktop. The device-authorization flow yields a bootstrap
// machine token (scope app:cdrop, no meta) that proves the user's identity but is not a
// machine token (scope app:commilitia-drop, no meta) that proves the user's identity but is not a
// cdrop-managed device. This call exchanges it for a managed device session bound to this
// device's stable device_id, so the desktop joins cdrop's unified device list and is
// managed exactly like a browser — the same model, not a separate native-only track.
+2 -2
View File
@@ -46,7 +46,7 @@ func writeInto(dir, name string, data []byte) (string, error) {
}
safe := sanitizeFileName(name)
if safe == "" {
safe = "cdrop-download"
safe = "Commilitia Drop Download"
}
target := uniquePath(dir, safe)
if err := os.WriteFile(target, data, 0o644); err != nil {
@@ -248,7 +248,7 @@ func FinalizeStreamingDownload(sessionId, name string) (string, error) {
}
safe := sanitizeFileName(name)
if safe == "" {
safe = "cdrop-download"
safe = "Commilitia Drop Download"
}
target := uniquePath(d.dir, safe)
if err := os.Rename(d.tmp, target); err != nil {
+1 -1
View File
@@ -69,7 +69,7 @@ func HealIdentity(s *LoginResult, apiBase string) *LoginResult {
}
if changed {
if err := SaveSession(*s); err != nil {
slog.Warn("cdrop: heal identity save failed", "err", err)
slog.Warn("Commilitia Drop: heal identity save failed", "err", err)
}
}
return s
+36 -3
View File
@@ -10,8 +10,8 @@ import (
"strings"
)
// launchAgentLabel is the reverse-DNS label + plist filename for the per-user
// LaunchAgent that starts cdrop at login.
// launchAgentLabel is a stable internal identifier. The product name shown to
// users comes from the application metadata, not this LaunchAgent label.
const launchAgentLabel = "net.commilitia.cdrop"
func launchAgentPath() (string, error) {
@@ -71,7 +71,40 @@ func SetLaunchAtLogin(enabled bool) error {
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
return err
}
return os.WriteFile(p, []byte(buildLaunchAgentPlist(launchAgentLabel, args)), 0o644)
if err := writeLaunchAgentAtomically(
p,
[]byte(buildLaunchAgentPlist(launchAgentLabel, args)),
); err != nil {
return err
}
return nil
}
func writeLaunchAgentAtomically(path string, contents []byte) error {
dir := filepath.Dir(path)
f, err := os.CreateTemp(dir, "."+filepath.Base(path)+".tmp-*")
if err != nil {
return err
}
tempPath := f.Name()
defer os.Remove(tempPath)
if err := f.Chmod(0o644); err != nil {
f.Close()
return err
}
if _, err := f.Write(contents); err != nil {
f.Close()
return err
}
if err := f.Sync(); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
return os.Rename(tempPath, path)
}
// IsLaunchAtLoginEnabled reports whether the LaunchAgent plist is present.
+2 -2
View File
@@ -48,8 +48,8 @@ func TestSetLaunchAtLoginInstallsAndRemoves(t *testing.T) {
func TestAppBundlePath(t *testing.T) {
cases := map[string]string{
"/Apps/cdrop.app/Contents/MacOS/desktop": "/Apps/cdrop.app",
"/usr/local/bin/desktop": "",
"/Apps/Commilitia Drop.app/Contents/MacOS/Commilitia Drop": "/Apps/Commilitia Drop.app",
"/usr/local/bin/Commilitia Drop": "",
}
for in, want := range cases {
if got := appBundlePath(in); got != want {
+2 -2
View File
@@ -1,6 +1,6 @@
#import <Network/Network.h>
// cdropTriggerLocalNetwork:起一个对 _cdrop._tcp 的 Bonjour 浏览,触发 macOS 本地网络权限弹窗。
// cdropTriggerLocalNetwork:起一个对 _commilitia-drop._tcp 的 Bonjour 浏览,触发 macOS 本地网络权限弹窗。
// WKWebView 自身不会触发该权限请求,须由宿主 App 主动发起一次本地网络访问;授权后本进程内的
// WebRTC 才能收集 host / mDNS 候选实现同内网直连(见 localnetwork_darwin.go 注释)。浏览结果本身
// 不关心——「发起访问」这一动作即触发授权。保活单个 browser(静态全局,ARC 下持有),幂等。
@@ -11,7 +11,7 @@ void cdropTriggerLocalNetwork(void) {
return;
}
nw_browse_descriptor_t descriptor =
nw_browse_descriptor_create_bonjour_service("_cdrop._tcp", NULL);
nw_browse_descriptor_create_bonjour_service("_commilitia-drop._tcp", NULL);
nw_parameters_t parameters = nw_parameters_create();
nw_parameters_set_include_peer_to_peer(parameters, true);
+4
View File
@@ -13,6 +13,10 @@ import "C"
import "unsafe"
// InitializeNotifications is a no-op on macOS; the notification center is
// initialized lazily by the native framework.
func InitializeNotifications() {}
// Notify shows a native system notification.
//
// macOS uses UNUserNotificationCenter, which REQUIRES the app bundle to be
+3
View File
@@ -2,6 +2,9 @@
package platform
// InitializeNotifications is a no-op on unsupported desktop platforms.
func InitializeNotifications() {}
// Notify is a no-op on platforms without a native notification backend wired up.
// The desktop client targets macOS and Windows; this keeps the package building
// on other GOOS (e.g. a Linux `go vet` / CI pass).
+34 -9
View File
@@ -4,14 +4,18 @@ package platform
import (
"os"
"path/filepath"
"sync"
toast "git.sr.ht/~jackmordaunt/go-toast/v2"
"golang.org/x/sys/windows/registry"
)
const (
// toastAppID 是 Windows Action Center 里显示的应用标识,与 macOS bundle id 对齐。
toastAppID = "net.commilitia.cdrop"
// toastAppID 是稳定的内部 AppUserModelIDWindows 通知设置中的用户可见名称由
// toastDisplayName 单独写入 DisplayName,避免为了改显示名破坏系统身份。
toastAppID = "net.commilitia.cdrop"
toastDisplayName = "Commilitia Drop"
// toastGUID 固定不变——它把通知归属到注册表里的本应用条目;更换会让既有通知
// 失去归属。
toastGUID = "{c4d8e2a1-6b3f-4e7a-9c2d-1f5b8a0e3d6c}"
@@ -19,20 +23,41 @@ const (
var toastInit sync.Once
// Notify shows a native Windows toast. go-toast renders via the WinRT/COM path
// (PowerShell fallback when the AppID isn't registered), so it works without code
// signing — SmartScreen only gates the installer, not notifications. Best-effort:
// errors are swallowed. SetAppData registers the app identity once so the toast
// shows "cdrop" rather than the PowerShell host.
func Notify(title, body string) {
// InitializeNotifications registers the stable Windows notification identity
// and corrects its user-visible name. It is safe to call repeatedly.
func InitializeNotifications() {
toastInit.Do(func() {
data := toast.AppData{AppID: toastAppID, GUID: toastGUID}
if exe, err := os.Executable(); err == nil {
data.ActivationExe = exe
}
_ = toast.SetAppData(data)
})
// go-toast v2.0.3 没有独立 DisplayName 字段,会把 AppID 写进
// DisplayName,且已有值时不更新;在同一稳定键上显式覆盖显示名。
appKey := filepath.Join(
"SOFTWARE",
"Classes",
"AppUserModelId",
toastAppID,
)
if key, err := registry.OpenKey(
registry.CURRENT_USER,
appKey,
registry.SET_VALUE,
); err == nil {
_ = key.SetStringValue("DisplayName", toastDisplayName)
_ = key.Close()
}
})
}
// Notify shows a native Windows toast. go-toast renders via the WinRT/COM path
// (PowerShell fallback when the AppID isn't registered), so it works without code
// signing — SmartScreen only gates the installer, not notifications. Best-effort:
// errors are swallowed.
func Notify(title, body string) {
InitializeNotifications()
n := toast.Notification{
AppID: toastAppID,
Title: title,
+5 -5
View File
@@ -26,7 +26,7 @@ import (
// OAuthConfig carries the Auth Broker coordinates. BrokerURL is the broker's PUBLIC
// origin (e.g. https://sso.commilitia.net); App is this app's key in the broker apps
// registry ("cdrop"). The app's loopback redirect must be registered in the broker's
// registry ("commilitia-drop"). The app's loopback redirect must be registered in the broker's
// apps.json (redirect_uris) for the device flow to accept it.
type OAuthConfig struct {
BrokerURL string
@@ -138,7 +138,7 @@ func (f *Flow) Login(ctx context.Context) (*TokenResult, error) {
"state": {state},
"code_challenge": {challenge},
"code_challenge_method": {"S256"},
"description": {"cdrop 桌面客户端"},
"description": {"Commilitia Drop"},
}.Encode()
f.openURL(authURL)
@@ -255,11 +255,11 @@ func randString(n int) (string, error) {
// the real affordance; the close attempt is best-effort.
func writeClosePage(w http.ResponseWriter, ok bool) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
msg := "已登录,可关闭本页返回 cdrop。"
msg := "已登录,可关闭本页返回 Commilitia Drop。"
if !ok {
msg = "登录未完成,可关闭本页返回 cdrop 重试。"
msg = "登录未完成,可关闭本页返回 Commilitia Drop 重试。"
}
fmt.Fprintf(w, `<!doctype html><html lang="zh-Hans"><head><meta charset="utf-8"><title>cdrop</title></head>`+
fmt.Fprintf(w, `<!doctype html><html lang="zh-Hans"><head><meta charset="utf-8"><title>Commilitia Drop</title></head>`+
`<body style="font-family:system-ui,sans-serif;text-align:center;margin-top:20vh">`+
`<p>%s</p><script>setTimeout(function(){window.close();},800);</script></body></html>`, msg)
}
+6 -6
View File
@@ -55,7 +55,7 @@ func TestLogin_Success(t *testing.T) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "sid-1",
"app": "cdrop",
"app": "commilitia-drop",
"access": "at-123",
"refresh": "rtk-456",
"access_expires": time.Now().Add(15 * time.Minute).Unix(),
@@ -63,7 +63,7 @@ func TestLogin_Success(t *testing.T) {
}))
defer brokerSrv.Close()
cfg := OAuthConfig{BrokerURL: brokerSrv.URL, App: "cdrop"}
cfg := OAuthConfig{BrokerURL: brokerSrv.URL, App: "commilitia-drop"}
// Fake browser: parse the /device/authorize URL, assert it carries app + PKCE,
// then GET the loopback redirect with a code + the same state (approved).
@@ -80,7 +80,7 @@ func TestLogin_Success(t *testing.T) {
if q.Get("code_challenge") == "" || q.Get("code_challenge_method") != "S256" {
t.Errorf("authorize request missing PKCE challenge: %v", q)
}
if q.Get("app") != "cdrop" {
if q.Get("app") != "commilitia-drop" {
t.Errorf("authorize app = %q", q.Get("app"))
}
cb := q.Get("redirect_uri") + "?code=auth-code-xyz&state=" + url.QueryEscape(q.Get("state"))
@@ -119,7 +119,7 @@ func TestLogin_Success(t *testing.T) {
}
func TestLogin_StateMismatch(t *testing.T) {
cfg := OAuthConfig{BrokerURL: "https://sso.example.net", App: "cdrop"}
cfg := OAuthConfig{BrokerURL: "https://sso.example.net", App: "commilitia-drop"}
openURL := func(authURL string) {
u, _ := url.Parse(authURL)
redirect := u.Query().Get("redirect_uri")
@@ -157,7 +157,7 @@ func TestRefresh_Success(t *testing.T) {
}))
defer brokerSrv.Close()
cfg := OAuthConfig{BrokerURL: brokerSrv.URL, App: "cdrop"}
cfg := OAuthConfig{BrokerURL: brokerSrv.URL, App: "commilitia-drop"}
tok, err := NewFlow(cfg, func(string) {}).Refresh(context.Background(), "old-rtk")
if err != nil {
t.Fatalf("Refresh: %v", err)
@@ -174,7 +174,7 @@ func TestRefresh_Success(t *testing.T) {
}
func TestRefresh_EmptyToken(t *testing.T) {
cfg := OAuthConfig{BrokerURL: "https://sso.example.net", App: "cdrop"}
cfg := OAuthConfig{BrokerURL: "https://sso.example.net", App: "commilitia-drop"}
if _, err := NewFlow(cfg, func(string) {}).Refresh(context.Background(), ""); err == nil {
t.Fatal("want error for empty refresh token")
}
+8 -4
View File
@@ -177,7 +177,11 @@ func SaveSession(res LoginResult) error {
if enc, err := encryptToken(res.RefreshToken); err == nil {
rec.RefreshTokenEnc = enc
} else {
slog.Warn("cdrop: refresh_token kept in session file; OS secret store unavailable", "err", err)
slog.Warn(
"Commilitia Drop: refresh_token kept in session file; OS secret store unavailable",
"err",
err,
)
rec.RefreshToken = res.RefreshToken
}
}
@@ -226,7 +230,7 @@ func LoadSession() (*LoginResult, error) {
// still unavailable, leaving the file as-is).
res.RefreshToken = rec.RefreshToken
if err := SaveSession(*res); err != nil {
slog.Warn("cdrop: refresh_token migration to secret store failed", "err", err)
slog.Warn("Commilitia Drop: refresh_token migration to secret store failed", "err", err)
}
case rec.RefreshTokenEnc != "":
if rt, err := decryptToken(rec.RefreshTokenEnc); err == nil {
@@ -234,7 +238,7 @@ func LoadSession() (*LoginResult, error) {
} else {
// Key gone / ciphertext corrupt: drop to an access-token-only session;
// the app will require a fresh login once the access_token lapses.
slog.Warn("cdrop: decrypt refresh_token failed; re-login will be required", "err", err)
slog.Warn("Commilitia Drop: decrypt refresh_token failed; re-login will be required", "err", err)
}
}
return res, nil
@@ -252,7 +256,7 @@ func ClearSession() error {
}
if err := keyring.Delete(keyringService, keyringKeyAccount); err != nil &&
!errors.Is(err, keyring.ErrNotFound) {
slog.Warn("cdrop: clear session key from secret store failed", "err", err)
slog.Warn("Commilitia Drop: clear session key from secret store failed", "err", err)
}
return nil
}
+1 -1
View File
@@ -4,7 +4,7 @@ package platform
/*
#cgo darwin CFLAGS: -x objective-c -fobjc-arc
#cgo darwin LDFLAGS: -framework Cocoa
#cgo darwin LDFLAGS: -framework Cocoa -framework UniformTypeIdentifiers
#include <stdlib.h>
void cdropStatusBarInstall(const void *iconPNG, int iconLen, const char *title,
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 2 ]]
then
echo "用法:$0 <Commilitia Drop.app> <Commilitia Drop.dmg>" >&2
exit 2
fi
appPath="$1"
outputPath="$2"
if [[ ! -d "$appPath" ]]
then
echo "找不到应用:$appPath" >&2
exit 1
fi
stagingDirectory="$(mktemp -d /tmp/commilitia-drop-dmg.XXXXXX)"
trap 'rm -rf "$stagingDirectory"' EXIT
ditto "$appPath" "$stagingDirectory/Commilitia Drop.app"
ln -s /Applications "$stagingDirectory/Applications"
mkdir -p "$(dirname "$outputPath")"
rm -f "$outputPath"
hdiutil create \
-volname "Commilitia Drop" \
-srcfolder "$stagingDirectory" \
-ov \
-format UDZO \
"$outputPath"
+3 -3
View File
@@ -1,13 +1,13 @@
{
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "Commilitia Drop Desktop",
"outputfilename": "Commilitia Drop Desktop",
"name": "Commilitia Drop",
"outputfilename": "Commilitia Drop",
"frontend:install": "cd ../../web && npm install",
"frontend:build": "cd ../../web && npm run build -- --outDir ../desktop/frontend/dist --emptyOutDir",
"frontend:dev:watcher": "cd ../../web && npm run dev",
"frontend:dev:serverUrl": "auto",
"info": {
"productName": "Commilitia Drop Desktop"
"productName": "Commilitia Drop"
},
"author": {
"name": "commilitia",