@yufengtadian/freedom-cli 1.12.18 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/lib/build.js +1 -1
  2. package/lib/security.js +1 -1
  3. package/package.json +5 -1
  4. package/shell/darwin-arm64/freedom-shell +0 -0
  5. package/shell/linux-x64/freedom-shell +0 -0
  6. package/shell/win-x64/freedom-shell.exe +0 -0
  7. package/templates/go/go.mod +1 -7
  8. package/templates/go/go.sum +2 -2
  9. package/templates/go/main.go +22 -8
  10. package/templates/go/pkg/freedom/anti_debug_other.go +2 -3
  11. package/templates/go/pkg/freedom/anti_debug_windows.go +10 -12
  12. package/templates/go/pkg/freedom/assets/{index.html → default.html} +1 -1
  13. package/templates/go/pkg/freedom/assets/freedom.js +200 -5
  14. package/templates/go/pkg/freedom/assets_embed.go +11 -13
  15. package/templates/go/pkg/freedom/authenticode_other.go +11 -0
  16. package/templates/go/pkg/freedom/authenticode_windows.go +87 -0
  17. package/templates/go/pkg/freedom/backend_proc.go +291 -53
  18. package/templates/go/pkg/freedom/bridge.go +13 -2
  19. package/templates/go/pkg/freedom/capability.go +77 -0
  20. package/templates/go/pkg/freedom/center_other.go +3 -0
  21. package/templates/go/pkg/freedom/center_windows.go +45 -35
  22. package/templates/go/pkg/freedom/dispatch.go +60 -0
  23. package/templates/go/pkg/freedom/events_windows.go +319 -0
  24. package/templates/go/pkg/freedom/freedom.go +193 -99
  25. package/templates/go/pkg/freedom/msgwindow_windows.go +257 -0
  26. package/templates/go/pkg/freedom/osver_other.go +15 -0
  27. package/templates/go/pkg/freedom/osver_windows.go +121 -0
  28. package/templates/go/pkg/freedom/proc_hide_windows.go +2 -2
  29. package/templates/go/pkg/freedom/{configfile.go → resources.go} +38 -35
  30. package/templates/go/pkg/freedom/security.go +19 -12
  31. package/templates/go/pkg/freedom/singleinstance_other.go +10 -0
  32. package/templates/go/pkg/freedom/singleinstance_windows.go +145 -0
  33. package/templates/go/pkg/freedom/spawn_other.go +11 -0
  34. package/templates/go/pkg/freedom/spawn_windows.go +23 -0
  35. package/templates/go/pkg/freedom/store.go +458 -0
  36. package/templates/go/pkg/freedom/syscap_linux.go +278 -0
  37. package/templates/go/pkg/freedom/syscap_other.go +15 -3
  38. package/templates/go/pkg/freedom/syscap_windows.go +228 -36
  39. package/templates/go/pkg/freedom/sysint_common.go +70 -0
  40. package/templates/go/pkg/freedom/sysint_windows.go +426 -0
  41. package/templates/go/pkg/freedom/tray_common.go +12 -0
  42. package/templates/go/pkg/freedom/tray_linux.go +302 -0
  43. package/templates/go/pkg/freedom/tray_other.go +2 -2
  44. package/templates/go/pkg/freedom/tray_windows.go +69 -39
  45. package/templates/go/pkg/freedom/updater.go +372 -0
  46. package/templates/go/pkg/freedom/window_mgr.go +385 -0
  47. package/templates/go/pkg/freedom/window_other.go +32 -66
  48. package/templates/go/pkg/freedom/window_windows.go +397 -254
  49. package/templates/go/build_all.txt +0 -0
  50. package/templates/go/build_err.txt +0 -0
  51. package/templates/go/gen_config.go +0 -22
  52. package/templates/go/pkg/freedom/center_windows_test.go +0 -32
  53. package/templates/go/pkg/freedom/security_test.go +0 -153
  54. package/templates/go/temp_vet_test/main.go +0 -40
  55. package/templates/go/test_err.txt +0 -0
  56. package/templates/go/vet_err.txt +0 -1
  57. package/templates/go/vet_err2.txt +0 -2
  58. package/templates/go/webview_go/.github/workflows/ci.yaml +0 -62
  59. package/templates/go/webview_go/CHANGELOG.md +0 -15
  60. package/templates/go/webview_go/LICENSE +0 -22
  61. package/templates/go/webview_go/README.md +0 -50
  62. package/templates/go/webview_go/examples/basic/main.go +0 -12
  63. package/templates/go/webview_go/examples/bind/main.go +0 -38
  64. package/templates/go/webview_go/glue.c +0 -36
  65. package/templates/go/webview_go/go.mod +0 -3
  66. package/templates/go/webview_go/go.sum +0 -0
  67. package/templates/go/webview_go/libs/mswebview2/LICENSE +0 -27
  68. package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +0 -23568
  69. package/templates/go/webview_go/libs/mswebview2/include/vendor.go +0 -2
  70. package/templates/go/webview_go/libs/mswebview2/vendor.go +0 -2
  71. package/templates/go/webview_go/libs/mswebview2/version.txt +0 -1
  72. package/templates/go/webview_go/libs/webview/LICENSE +0 -22
  73. package/templates/go/webview_go/libs/webview/include/vendor.go +0 -2
  74. package/templates/go/webview_go/libs/webview/include/webview.h +0 -3871
  75. package/templates/go/webview_go/libs/webview/vendor.go +0 -2
  76. package/templates/go/webview_go/libs/webview/version.txt +0 -1
  77. package/templates/go/webview_go/webview.cc +0 -1
  78. package/templates/go/webview_go/webview.go +0 -481
  79. package/templates/go/webview_go/webview_test.go +0 -50
package/lib/build.js CHANGED
@@ -434,7 +434,7 @@ function appVersionFromPkg(dir) {
434
434
  }
435
435
  }
436
436
 
437
- // 渲染 resources/config.json(与壳 configfile.go 的 runtimeConfigFile 字段对齐)
437
+ // 渲染 resources/config.json(与壳 resources.go 的 runtimeConfigFile 字段对齐)
438
438
  function renderConfigJSON(cfg, name) {
439
439
  const obj = {
440
440
  name,
package/lib/security.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // 提取主密钥并复现派生算法"。真正"每应用唯一密钥"需要每应用独立编译壳
17
17
  // (破坏通用壳 + 零工具链架构),不在本模式范围内。
18
18
  //
19
- // ⚠️ 跨语言同步点:本文件与 templates/go/pkg/freedom/security.go 必须保持
19
+ // ⚠️ 跨语言同步点:本文件与 templates/go/pkg/freedom/security.go(= 仓库根 security.go)必须保持
20
20
  // MASTER_KEY / DERIVE_SALT / PBKDF2_ITER / KEY_LEN / APP_BIN_MAGIC
21
21
  // 以及加密算法(aes-256-ctr + hmac-sha256)完全一致。
22
22
  // 修改任一侧必须同步另一侧,否则 build 出的加密资源壳无法解密。
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@yufengtadian/freedom-cli",
3
- "version": "1.12.18",
3
+ "version": "1.13.0",
4
4
  "description": "Freedom WebView desktop shell packaging tool - no Go toolchain required, one command packs three-platform desktop apps",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/YUfeng12TA/freedom.git"
8
+ },
5
9
  "keywords": [
6
10
  "desktop",
7
11
  "webview",
Binary file
Binary file
Binary file
@@ -1,11 +1,5 @@
1
1
  module freedom-cli-shell
2
2
 
3
- go 1.22
3
+ go 1.26.5
4
4
 
5
5
  require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
6
-
7
- require golang.org/x/sys v0.28.0
8
-
9
- // Freedom fork: 使用随包分发的本地 webview_go(标题栏图标与 exe 图标一致、
10
- // 无边框窗口最大化限定工作区、彻底清理非客户区等定制),避免拉取上游被覆盖。
11
- replace github.com/webview/webview_go => ./webview_go
@@ -1,2 +1,2 @@
1
- golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
2
- golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1
+ github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
2
+ github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
@@ -1,13 +1,27 @@
1
+ // Freedom 通用壳源码模板(templates/go)。
2
+ //
3
+ // pkg/freedom 为框架源码快照(与 npm 包版本同步发布);main 是预编译通用壳入口:
4
+ // 应用内容全部来自 exe 同目录 resources/(CLI build 写入 index.html / config.json /
5
+ // app.bin),运行时由框架 resources.go 覆盖到实际配置。
6
+ //
7
+ // 用途:`freedom shell build <plat>` 在本目录编译对应平台壳二进制(需 Go 工具链,
8
+ // webview_go 依赖系统 WebView 框架,仅能编本机平台);日常打包走
9
+ // 包内 shell/<plat>/ 或 GitHub Releases 下载,无需本模板。
1
10
  package main
2
11
 
3
- import "freedom-cli-shell/pkg/freedom"
12
+ import (
13
+ "flag"
14
+ "fmt"
15
+
16
+ "freedom-cli-shell/pkg/freedom"
17
+ )
4
18
 
5
- // main 是 Freedom 壳层应用入口。
6
- // 窗口与标题栏配置来自 build 阶段生成的 gen_config.go(appConfig),
7
- // 前端页面运行时从 exe 同目录 resources/index.html 加载(build 阶段由 CLI
8
- // 把打包后的 index.html 与 config.json 写入该目录,壳 SetHtml 内存加载、不占端口)。
9
19
  func main() {
10
- cfg := appConfig()
11
- app := freedom.New(cfg)
12
- app.Run()
20
+ showVersion := flag.Bool("version", false, "打印壳版本号并退出")
21
+ flag.Parse()
22
+ if *showVersion {
23
+ fmt.Println(freedom.Version)
24
+ return
25
+ }
26
+ freedom.New(freedom.Config{}).Run()
13
27
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  package freedom
4
4
 
5
- // 反调试(非 Windows 平台空实现)。
6
- // macOS/Linux 的调试器检测(ptrace / task_for_pid 等)依赖平台特定 API,
7
- // 本版本不做;如需加强可在对应平台文件扩展。
5
+ // 反调试(非 Windows 平台空实现)。macOS/Linux 的调试器检测依赖
6
+ // ptrace / task_for_pid 等平台特定 API,本版本不做;如需加强在对应平台文件扩展。
8
7
  func antiDebugCheck() {}
@@ -2,9 +2,9 @@
2
2
 
3
3
  package freedom
4
4
 
5
- // 反调试(Windows 实现):high 安全模式下调用。
6
- // 检测常见用户态调试器,发现时静默退出,防止攻击者在调试器下
7
- // 单步追踪资源解密 / 密钥派生逻辑。
5
+ // 反调试(Windows 实现):high 安全模式下由 Run() 调用。
6
+ // 检测常见调试器,命中时静默退出,防止攻击者在调试器下单步追踪
7
+ // 资源解密 / 密钥派生逻辑。
8
8
 
9
9
  import (
10
10
  "os"
@@ -13,27 +13,25 @@ import (
13
13
  )
14
14
 
15
15
  var (
16
- kernel32dbg = syscall.NewLazyDLL("kernel32.dll")
17
- procIsDebuggerPresent = kernel32dbg.NewProc("IsDebuggerPresent")
18
- procCheckRemoteDebuggerPresent = kernel32dbg.NewProc("CheckRemoteDebuggerPresent")
16
+ kernel32AntiDebug = syscall.NewLazyDLL("kernel32.dll")
17
+ procIsDebuggerPresent = kernel32AntiDebug.NewProc("IsDebuggerPresent")
18
+ procCheckRemoteDebuggerPresent = kernel32AntiDebug.NewProc("CheckRemoteDebuggerPresent")
19
19
  )
20
20
 
21
21
  // antiDebugCheck 检测调试器;发现则立即退出进程(退出码 77,无提示)。
22
- // 仅在 high 安全模式启用时由 freedom.go Run() 调用。
23
22
  func antiDebugCheck() {
24
23
  if isBeingDebugged() {
25
24
  os.Exit(77)
26
25
  }
27
26
  }
28
27
 
29
- // isBeingDebugged 同时检测本地调试器(IsDebuggerPresent)与远程/内核调试器
30
- // (CheckRemoteDebuggerPresent),任一命中即认为正在被调试。
28
+ // isBeingDebugged 同时检测用户态调试器(IsDebuggerPresent)与远程/内核调试器
29
+ // (CheckRemoteDebuggerPresent)。hProcess 用 GetCurrentProcess 伪句柄(-1)。
31
30
  func isBeingDebugged() bool {
32
- r, _, _ := procIsDebuggerPresent.Call()
33
- if r != 0 {
31
+ if r, _, _ := procIsDebuggerPresent.Call(); r != 0 {
34
32
  return true
35
33
  }
36
34
  var present int32
37
- procCheckRemoteDebuggerPresent.Call(uintptr(0), uintptr(unsafe.Pointer(&present)))
35
+ procCheckRemoteDebuggerPresent.Call(^uintptr(0), uintptr(unsafe.Pointer(&present)))
38
36
  return present != 0
39
37
  }
@@ -45,7 +45,7 @@
45
45
  font-size: 13px;
46
46
  }
47
47
 
48
- /* 无边框模式:前端自绘标题栏(frameless 时显示) */
48
+ /* 无边框模式:前端自绘标题栏(hidden / frameless 时显示) */
49
49
  .titlebar {
50
50
  display: none;
51
51
  height: 36px;
@@ -11,13 +11,27 @@
11
11
 
12
12
  var listeners = {};
13
13
 
14
+ // M1 异步桥接协议:__freedom_bridge(id, method, paramsJson) 只做投递(ack),
15
+ // Go 侧完成后经 freedom.__resolve(id, {ok,result|error}) 回写本表登记的 Promise。
16
+ var callSeq = 0;
17
+ var pendingCalls = {};
18
+
14
19
  function call(method) {
15
20
  var params = Array.prototype.slice.call(arguments, 1);
16
21
  var bridge = getBridge();
17
22
  if (!bridge) {
18
23
  return Promise.reject(new Error('[freedom] 未检测到原生桥接(__freedom_bridge),当前不在桌面壳内运行。'));
19
24
  }
20
- return bridge(method, JSON.stringify(params));
25
+ var id = ++callSeq;
26
+ return new Promise(function (resolve, reject) {
27
+ pendingCalls[id] = { resolve: resolve, reject: reject };
28
+ Promise.resolve(bridge(id, method, JSON.stringify(params))).catch(function (e) {
29
+ if (pendingCalls[id]) {
30
+ delete pendingCalls[id];
31
+ reject(e);
32
+ }
33
+ });
34
+ });
21
35
  }
22
36
 
23
37
  function on(event, cb) {
@@ -32,6 +46,18 @@
32
46
  if (i >= 0) l.splice(i, 1);
33
47
  }
34
48
 
49
+ // 一次性订阅:触发一次后自动退订(对标 Tauri listen+unlisten)。
50
+ // 注意:必须用"注册进数组的那个 wrapper"退订——on() 返回的是 unlisten
51
+ // 闭包而非回调本身,拿它去 off() 删不掉监听器(旧 bug,tests/sdk-surface 锁定)。
52
+ function once(event, cb) {
53
+ if (typeof cb !== 'function') return function () {};
54
+ var fn = function (data) {
55
+ off(event, fn);
56
+ cb(data);
57
+ };
58
+ return on(event, fn);
59
+ }
60
+
35
61
  // 后端通过 app.Emit(event, data) 触发(Eval 调用本函数)。
36
62
  function emit(event, data) {
37
63
  var l = listeners[event] || [];
@@ -40,12 +66,24 @@
40
66
  }
41
67
  }
42
68
 
69
+ // Go 侧异步回写入口(dispatch.go 的 resolveJS 经 Eval 调用)。
70
+ // 拒绝值保持旧契约:webview_go 时代 catch 到的是错误消息字符串。
71
+ function resolveCall(id, env) {
72
+ var p = pendingCalls[id];
73
+ if (!p) return; // 页面已重载/回写迟到:静默丢弃
74
+ delete pendingCalls[id];
75
+ if (env && env.ok) p.resolve(env.result);
76
+ else p.reject((env && env.error) || '[freedom] call failed');
77
+ }
78
+
43
79
  var freedom = {
44
80
  call: call,
45
81
  invoke: call,
46
82
  on: on,
47
83
  off: off,
84
+ once: once,
48
85
  emit: emit,
86
+ __resolve: resolveCall,
49
87
  window: {
50
88
  // 窗口控制(无边框/隐藏标题栏模式下前端自绘按钮使用)。
51
89
  // 全部经桥接调用原生实现,返回 Promise。
@@ -53,12 +91,168 @@
53
91
  maximize: function () { return windowAction('maximize'); },
54
92
  unmaximize: function () { return windowAction('unmaximize'); },
55
93
  toggleMaximize: function () { return windowAction('toggleMaximize'); },
56
- close: function () { return windowAction('close'); },
94
+ // close(force):force=true 时绕过 interceptClose 拦截直接关闭。
95
+ close: function (force) { return windowAction('close', { force: !!force }); },
57
96
  isMaximized: function () { return windowAction('isMaximized'); },
58
97
  isFrameless: function () { return windowAction('isFrameless'); },
98
+ // —— W1 对标 Tauri:位置 / 尺寸(物理像素)——
99
+ setPosition: function (x, y) { return windowAction('setPosition', { x: x, y: y }); },
100
+ setSize: function (width, height) { return windowAction('setSize', { width: width, height: height }); },
101
+ getPosition: function () { return windowAction('getPosition'); },
102
+ getSize: function () { return windowAction('getSize'); },
103
+ innerSize: function () { return windowAction('innerSize'); },
104
+ center: function () { return windowAction('center'); },
105
+ setTitle: function (title) { return windowAction('setTitle', { title: String(title) }); },
106
+ // —— 可见性 / 层级 / 焦点 ——
107
+ show: function () { return windowAction('show'); },
108
+ hide: function () { return windowAction('hide'); },
109
+ focus: function () { return windowAction('focus'); },
110
+ isVisible: function () { return windowAction('isVisible'); },
111
+ isFocused: function () { return windowAction('isFocused'); },
112
+ isMinimized: function () { return windowAction('isMinimized'); },
113
+ setAlwaysOnTop: function (on) { return windowAction('setAlwaysOnTop', { on: !!on }); },
114
+ setSkipTaskbar: function (on) { return windowAction('setSkipTaskbar', { on: !!on }); },
115
+ // —— 行为开关 ——
116
+ setResizable: function (on) { return windowAction('setResizable', { on: !!on }); },
117
+ setMaximizable: function (on) { return windowAction('setMaximizable', { on: !!on }); },
118
+ setMinimizable: function (on) { return windowAction('setMinimizable', { on: !!on }); },
119
+ // —— 全屏 / 关闭拦截 ——
120
+ setFullscreen: function (on) { return windowAction('setFullscreen', { on: !!on }); },
121
+ enterFullscreen: function () { return windowAction('setFullscreen', { on: true }); },
122
+ exitFullscreen: function () { return windowAction('setFullscreen', { on: false }); },
123
+ isFullscreen: function () { return windowAction('isFullscreen'); },
124
+ // 拦截系统/按钮关闭:开启后关闭动作变为 window.closeRequested 事件,
125
+ // 前端处理后以 close(true) 放行或不做操作保持窗口存活。
126
+ interceptClose: function (on) { return windowAction('interceptClose', { on: !!on }); },
127
+ // 聚合信息:title/outerX/outerY/outerWidth/outerHeight/innerWidth/innerHeight/
128
+ // scaleFactor/visible/focused/maximized/minimized/alwaysOnTop/skipTaskbar/frameless/fullscreen。
129
+ getInfo: function () { return windowAction('getInfo'); },
130
+ // 显示器列表(经 sys 桥接):[{x,y,width,height,workX,workY,workWidth,workHeight,scaleFactor,isPrimary}]
131
+ monitors: function () { return sysCall('window.monitors', {}); },
132
+ // 窗口视觉效果(Win11):backdrop: auto|none|solid|mica|acrylic;corner: round|roundSmall|square|default;borderColor: 0x00BBGGRR
133
+ setBackdrop: function (mode) { return sysCall('window.backdrop', { mode: mode }); },
134
+ setCorner: function (mode) { return sysCall('window.corner', { mode: mode }); },
135
+ setBorderColor: function (color) { return sysCall('window.borderColor', { color: color }); },
136
+ // —— M2 多窗口 ——
137
+ // id():本窗口标识('main' 或 'w1'…);create({title,width,height,center,url,html}):
138
+ // 异步拉起次级窗口返回 {id};list() 全部存活窗口;closeWindow/focusWindow(id)。
139
+ // create 的页面来源三选一:url(外链)> html(内联字符串)> 复制主页面——
140
+ // 主页面含开窗脚本时务必给 url 或 html,避免级联。
141
+ // 注意:次级窗口无 sys/tray 桥,托盘/热键/状态记忆等平台单例只挂主窗口;
142
+ // 次级窗口里 window.close() 关闭的是它自己。
143
+ id: function () { return windowAction('id'); },
144
+ list: function () { return windowAction('list'); },
145
+ create: function (opts) { return windowAction('create', opts || {}); },
146
+ closeWindow: function (id) { return windowAction('closeWindow', { id: id }); },
147
+ focusWindow: function (id) { return windowAction('focusWindow', { id: id }); },
148
+ },
149
+ // ---- W2 系统集成命名空间(均经 __freedom_sys / __freedom_tray 桥接)----
150
+ sys: sysCall,
151
+ clipboard: {
152
+ readText: function () { return sysCall('clipboard.read', {}); },
153
+ writeText: function (text) { return sysCall('clipboard.write', { text: String(text) }); },
154
+ },
155
+ shell: {
156
+ // 用系统默认程序打开 URL/文件(openExternal)。
157
+ open: function (target) { return sysCall('shell.open', { target: String(target) }); },
158
+ },
159
+ notification: {
160
+ show: function (title, body) { return sysCall('notification.show', { title: String(title), body: String(body || '') }); },
161
+ },
162
+ shortcut: {
163
+ // combo 形如 "ctrl+alt+k";触发时收到事件 freedom.on('shortcut.triggered', ({id}) => ...)。
164
+ register: function (id, combo) { return sysCall('shortcut.register', { id: id, combo: combo }); },
165
+ unregister: function (id) { return sysCall('shortcut.unregister', { id: id }); },
166
+ list: function () { return sysCall('shortcut.list', {}); },
167
+ },
168
+ autostart: {
169
+ isEnabled: function (name) { return sysCall('autostart.get', { name: name || '' }); },
170
+ set: function (enabled, opts) { return sysCall('autostart.set', { name: (opts && opts.name) || '', enabled: !!enabled, args: (opts && opts.args) || '' }); },
171
+ enable: function (opts) { return this.set(true, opts); },
172
+ disable: function (opts) { return this.set(false, opts); },
173
+ },
174
+ protocol: {
175
+ // URL Scheme 注册(deep link);拉起参数经 'app.secondInstance' 事件或 app.launchArgs() 获取。
176
+ register: function (scheme, displayName) { return sysCall('protocol.register', { scheme: scheme, name: displayName || '' }); },
177
+ unregister: function (scheme) { return sysCall('protocol.unregister', { scheme: scheme }); },
178
+ },
179
+ app: {
180
+ launchArgs: function () { return sysCall('app.launchArgs', {}); },
181
+ },
182
+ // ---- W3 数据层(对标 Tauri path / store / os / process)----
183
+ // kind ∈ config|data|cache|temp|home|exe;name 省略时用 AppID(目录名已消毒)。
184
+ path: function (kind, name) { return sysCall('path.get', { kind: kind, name: name || '' }); },
185
+ store: {
186
+ // 命名 JSON KV,落盘 <dataDir>/<name>.store.json,set 即持久化。
187
+ load: function (name) { return sysCall('store.load', { store: name || '' }); },
188
+ get: function (key, name) { return sysCall('store.get', { store: name || '', key: key }); },
189
+ set: function (key, value, name) { return sysCall('store.set', { store: name || '', key: key, value: value }); },
190
+ delete: function (key, name) { return sysCall('store.delete', { store: name || '', key: key }); },
191
+ keys: function (name) { return sysCall('store.keys', { store: name || '' }); },
192
+ },
193
+ os: {
194
+ info: function () { return sysCall('os.info', {}); }, // {platform,arch,hostname,osVersion,goVersion,numCPU}
195
+ },
196
+ process: {
197
+ id: function () { return sysCall('process.id', {}); },
198
+ exit: function (code) { return sysCall('process.exit', { code: code || 0 }); },
199
+ restart: function () { return sysCall('process.restart', {}); }, // 拉起新实例后退出的安全重启
200
+ },
201
+ // ---- G4 自动更新(对标 Tauri updater)----
202
+ // 检查/安装是异步长任务:调用只发起,结果经事件回推——
203
+ // 'update.available'({version,url,sha256,notes}) / 'update.upToDate'({current})
204
+ // / 'update.installed'({version,restartRequired}) / 'update.error'({message})
205
+ // install 只接受 check 时已通过 ed25519 验签的缓存条目;换装后需 process.restart 生效。
206
+ update: {
207
+ check: function () { return sysCall('update.check', {}); },
208
+ install: function () { return sysCall('update.install', {}); },
209
+ pending: function () { return sysCall('update.pending', {}); },
210
+ onAvailable: function (cb) { return on('update.available', cb); },
211
+ onUpToDate: function (cb) { return on('update.upToDate', cb); },
212
+ onInstalled: function (cb) { return on('update.installed', cb); },
213
+ onError: function (cb) { return on('update.error', cb); },
214
+ },
215
+ taskbar: {
216
+ setProgress: function (value) { return sysCall('taskbar.progress', { value: value }); },
217
+ setState: function (state) { return sysCall('taskbar.state', { state: state }); }, // normal|paused|error|indeterminate
218
+ clear: function () { return sysCall('taskbar.clear', {}); },
219
+ setOverlay: function (iconDataURL) { return sysCall('taskbar.overlay', { icon: iconDataURL }); },
220
+ clearOverlay: function () { return sysCall('taskbar.clearOverlay', {}); },
221
+ },
222
+ dialog: {
223
+ message: function (opts) { return sysCall('dialog.message', opts || {}); },
224
+ open: function (opts) { return sysCall('dialog.open', opts || {}); },
225
+ save: function (opts) { return sysCall('dialog.save', opts || {}); },
226
+ },
227
+ tray: {
228
+ // create({icon: dataURL, tooltip});事件:tray:click / tray:double-click / tray:menu({id})。
229
+ create: function (opts) { return trayCall('tray.create', opts || {}); },
230
+ destroy: function () { return trayCall('tray.destroy', {}); },
231
+ setTooltip: function (text) { return trayCall('tray.tooltip', { tooltip: text }); },
232
+ setMenu: function (items) { return trayCall('tray.menu', { items: items }); },
233
+ },
234
+ menu: {
235
+ // 原生菜单栏(挂主窗口):items 同 tray.setMenu 结构;点击走 'tray:menu' 事件。
236
+ set: function (items) { return trayCall('menu.set', { items: items }); },
59
237
  },
60
238
  };
61
239
 
240
+ function trayCall(method, args) {
241
+ if (typeof window.__freedom_tray !== 'function') {
242
+ return Promise.reject(new Error('[freedom] 未检测到托盘桥接。'));
243
+ }
244
+ return window.__freedom_tray(method, JSON.stringify(args || {}));
245
+ }
246
+
247
+ // sys 桥接:原生系统能力(taskbar.* / window.* / dialog.*)。
248
+ // W5 会扩展为 freedom.sys 命名空间,先行提供内部包装。
249
+ function sysCall(method, args) {
250
+ if (typeof window.__freedom_sys !== 'function') {
251
+ return Promise.reject(new Error('[freedom] 未检测到原生系统能力桥接。'));
252
+ }
253
+ return window.__freedom_sys(method, JSON.stringify(args || {}));
254
+ }
255
+
62
256
  // 前端自绘按钮的便捷绑定:把按钮 DOM 接到窗口控制。
63
257
  // freedom.window.bindButtons({ min: '#minBtn', max: '#maxBtn', close: '#closeBtn' })
64
258
  freedom.window.bindButtons = function (sel) {
@@ -75,12 +269,13 @@
75
269
  if (close) close.addEventListener('click', function () { self.close().catch(ignoreErr); });
76
270
  };
77
271
 
78
- function windowAction(action) {
79
- // __freedom_window 是 webview_go Bind 注入的异步全局函数(返回 Promise)
272
+ function windowAction(action, args) {
273
+ // __freedom_window 是 webview_go Bind 注入的异步全局函数(返回 Promise)。
274
+ // Go 侧签名为 (action, paramsJSON),paramsJSON 恒为 JSON object 字符串。
80
275
  if (typeof window.__freedom_window !== 'function') {
81
276
  return Promise.reject(new Error('[freedom] 未检测到原生窗口控制桥接。'));
82
277
  }
83
- return window.__freedom_window(action);
278
+ return window.__freedom_window(action, JSON.stringify(args || {}));
84
279
  }
85
280
 
86
281
  Object.defineProperty(freedom, 'isDesktop', {
@@ -2,20 +2,18 @@ package freedom
2
2
 
3
3
  import _ "embed"
4
4
 
5
- // jsSDK 是注入到每个前端页面的 Freedom 前端 SDK,暴露 window.freedom 全局对象。
6
- // 用法见 assets/freedom.js 顶部注释。
5
+ // jsSDK 是注入到每个前端页面的 Freedom 前端 SDK。
6
+ // 它暴露 window.freedom 全局对象:
7
7
  //
8
+ // window.freedom.call(method, ...args) -> Promise // 调用后端方法
9
+ // window.freedom.invoke(...) // call 的别名
10
+ // window.freedom.on(event, cb) -> unsubscribe // 订阅后端事件
11
+ // window.freedom.off(event, cb) // 取消订阅
12
+ // window.freedom.emit(event, data) // 应用内事件广播(后端 Eval 亦使用)
13
+ //
14
+ // bridge 由 go-webview2 的 Bind 机制注册,返回 Promise,Go 侧自动 JSON 编解码。
8
15
  //go:embed assets/freedom.js
9
16
  var jsSDK string
10
17
 
11
- // indexHTML 是内置的默认占位页。
12
- //
13
- // 预编译通用壳不嵌入应用专属页面:应用 HTML 来自 exe 同目录 resources/index.html
14
- //(freedom CLI build 时写入,见 resolveHTML 的加载优先级)。仅当外部页面缺失时
15
- // 回退到本占位页,保证"直接运行壳"也有可见 UI。
16
- //
17
- //go:embed assets/index.html
18
- var indexHTML string
19
-
20
- // defaultHTML 兼容框架默认占位页语义:无自定义前端时回退到 indexHTML。
21
- var defaultHTML = indexHTML
18
+ //go:embed assets/default.html
19
+ var defaultHTML string
@@ -0,0 +1,11 @@
1
+ //go:build !windows
2
+
3
+ package freedom
4
+
5
+ import "fmt"
6
+
7
+ // authenticodeCheck 非 Windows 平台不提供 Authenticode 校验能力。
8
+ // RequireSignature 开启时更新流会因此显式失败——静默跳过等于谎报已验签。
9
+ func authenticodeCheck(path string) error {
10
+ return fmt.Errorf("freedom: Authenticode 校验仅支持 Windows(当前平台无法完成 RequireSignature 验证)")
11
+ }
@@ -0,0 +1,87 @@
1
+ //go:build windows
2
+
3
+ package freedom
4
+
5
+ import (
6
+ "fmt"
7
+ "syscall"
8
+ "unsafe"
9
+ )
10
+
11
+ // M6 Authenticode 复核(wintrust!WinVerifyTrust):sha256 之外的可选发布者身份
12
+ // 二级信号——主信任锚仍是 manifest 的 ed25519 验签,此项属纵深防御
13
+ // (持哈希而不知签名私钥的被攻陷源会被拒)。
14
+ // 布局依 wintrust.h:WINTRUST_DATA / WINTRUST_FILE_INFO 字段序与自然对齐。
15
+
16
+ type wintrustFileInfo struct {
17
+ cbStruct uint32
18
+ pcwszFilePath *uint16
19
+ hFile uintptr
20
+ pgKnownSubject *syscall.GUID
21
+ }
22
+
23
+ type wintrustData struct {
24
+ cbStruct uint32
25
+ pPolicyCallbackData uintptr
26
+ pSIPClientData uintptr
27
+ dwUIChoice uint32
28
+ fdwRevocationChecks uint32
29
+ dwUnionChoice uint32
30
+ pFile *wintrustFileInfo
31
+ dwStateAction uint32
32
+ hWVTStateData uintptr
33
+ pwszURLReference *uint16
34
+ dwProvFlags uint32
35
+ dwUIContext uint32
36
+ }
37
+
38
+ const (
39
+ wtdUINone = 2 // WTD_UI_NONE:全程不弹 UI
40
+ wtdRevokeNone = 0x00000000 // 不检索吊销信息
41
+ wtdChoiceFile = 1 // WTD_CHOICE_FILE
42
+ wtdRevCheckNone = 0x00000010 // WTD_REVOCATION_CHECK_NONE(provFlags)
43
+ wtdStateClose = 0x00000002 // WTD_STATEACTION_CLOSE
44
+ wtdUIContextExec = 0 // WTD_UICONTEXT_EXECUTE
45
+ )
46
+
47
+ var (
48
+ wintrustDLL = syscall.NewLazyDLL("wintrust.dll")
49
+ procWinVerifyTrust = wintrustDLL.NewProc("WinVerifyTrust")
50
+ // WINTRUST_ACTION_GENERIC_VERIFY_V2 {00AAC56B-CD44-11d0-8CC2-006C9B2F61C7}
51
+ wtdActionGenericVerify = syscall.GUID{
52
+ Data1: 0x00AAC56B, Data2: 0xCD44, Data3: 0x11D0,
53
+ Data4: [8]byte{0x8C, 0xC2, 0x00, 0x6C, 0x9B, 0x2F, 0x61, 0xC7},
54
+ }
55
+ )
56
+
57
+ // authenticodeCheck 校验 PE 数字签名链。离线确定性由 WTD_REVOKE_NONE +
58
+ // WTD_REVOCATION_CHECK_NONE 保证(吊销检查需外网,且主信任已由 ed25519 覆盖)。
59
+ func authenticodeCheck(path string) error {
60
+ p, err := syscall.UTF16PtrFromString(path)
61
+ if err != nil {
62
+ return fmt.Errorf("freedom: 签名校验路径非法: %w", err)
63
+ }
64
+ fi := wintrustFileInfo{cbStruct: uint32(unsafe.Sizeof(wintrustFileInfo{})), pcwszFilePath: p}
65
+ wd := wintrustData{
66
+ cbStruct: uint32(unsafe.Sizeof(wintrustData{})),
67
+ dwUIChoice: wtdUINone,
68
+ fdwRevocationChecks: wtdRevokeNone,
69
+ dwUnionChoice: wtdChoiceFile,
70
+ pFile: &fi,
71
+ dwProvFlags: wtdRevCheckNone,
72
+ dwUIContext: wtdUIContextExec,
73
+ }
74
+ r, _, e := procWinVerifyTrust.Call(0,
75
+ uintptr(unsafe.Pointer(&wtdActionGenericVerify)),
76
+ uintptr(unsafe.Pointer(&wd)))
77
+ if wd.hWVTStateData != 0 { // 释放 verify 缓存的 provider 数据
78
+ wd.dwStateAction = wtdStateClose
79
+ procWinVerifyTrust.Call(0,
80
+ uintptr(unsafe.Pointer(&wtdActionGenericVerify)),
81
+ uintptr(unsafe.Pointer(&wd)))
82
+ }
83
+ if r != 0 {
84
+ return fmt.Errorf("freedom: Authenticode 校验未通过 (wintrust=0x%08X): %w", uint32(r), e)
85
+ }
86
+ return nil
87
+ }