@yufengtadian/freedom-cli 1.12.17 → 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.
- package/lib/build.js +74 -9
- package/lib/cli.js +61 -3
- package/lib/init.js +9 -1
- package/lib/security.js +2 -2
- package/lib/utils.js +5 -2
- package/lib/verify.js +236 -0
- package/package.json +43 -39
- package/shell/darwin-arm64/freedom-shell +0 -0
- package/shell/linux-x64/freedom-shell +0 -0
- package/shell/win-x64/freedom-shell.exe +0 -0
- package/templates/go/go.mod +1 -7
- package/templates/go/go.sum +2 -2
- package/templates/go/main.go +22 -8
- package/templates/go/pkg/freedom/anti_debug_other.go +2 -3
- package/templates/go/pkg/freedom/anti_debug_windows.go +10 -12
- package/templates/go/pkg/freedom/assets/{index.html → default.html} +1 -1
- package/templates/go/pkg/freedom/assets/freedom.js +219 -21
- package/templates/go/pkg/freedom/assets_embed.go +11 -13
- package/templates/go/pkg/freedom/authenticode_other.go +11 -0
- package/templates/go/pkg/freedom/authenticode_windows.go +87 -0
- package/templates/go/pkg/freedom/backend_proc.go +295 -51
- package/templates/go/pkg/freedom/bridge.go +13 -2
- package/templates/go/pkg/freedom/capability.go +77 -0
- package/templates/go/pkg/freedom/center_other.go +3 -0
- package/templates/go/pkg/freedom/center_windows.go +64 -36
- package/templates/go/pkg/freedom/dispatch.go +60 -0
- package/templates/go/pkg/freedom/events_windows.go +319 -0
- package/templates/go/pkg/freedom/freedom.go +242 -92
- package/templates/go/pkg/freedom/msgwindow_windows.go +257 -0
- package/templates/go/pkg/freedom/osver_other.go +15 -0
- package/templates/go/pkg/freedom/osver_windows.go +121 -0
- package/templates/go/pkg/freedom/proc_hide_windows.go +2 -2
- package/templates/go/pkg/freedom/{configfile.go → resources.go} +56 -32
- package/templates/go/pkg/freedom/security.go +19 -12
- package/templates/go/pkg/freedom/singleinstance_other.go +10 -0
- package/templates/go/pkg/freedom/singleinstance_windows.go +145 -0
- package/templates/go/pkg/freedom/spawn_other.go +11 -0
- package/templates/go/pkg/freedom/spawn_windows.go +23 -0
- package/templates/go/pkg/freedom/store.go +458 -0
- package/templates/go/pkg/freedom/syscap_linux.go +278 -0
- package/templates/go/pkg/freedom/syscap_other.go +22 -0
- package/templates/go/pkg/freedom/syscap_windows.go +845 -0
- package/templates/go/pkg/freedom/sysint_common.go +70 -0
- package/templates/go/pkg/freedom/sysint_windows.go +426 -0
- package/templates/go/pkg/freedom/tray_common.go +12 -0
- package/templates/go/pkg/freedom/tray_linux.go +302 -0
- package/templates/go/pkg/freedom/tray_other.go +10 -0
- package/templates/go/pkg/freedom/tray_windows.go +536 -0
- package/templates/go/pkg/freedom/updater.go +372 -0
- package/templates/go/pkg/freedom/window_mgr.go +385 -0
- package/templates/go/pkg/freedom/window_other.go +32 -66
- package/templates/go/pkg/freedom/window_windows.go +406 -251
- package/templates/project-minimal/freedom.config.js +58 -0
- package/templates/project-minimal/index.html +41 -0
- package/templates/project-minimal/package.json +14 -0
- package/templates/project-minimal/src/main.js +19 -0
- package/templates/project-minimal/vite.config.js +18 -0
- package/templates/go/gen_config.go +0 -22
- package/templates/go/pkg/freedom/security_test.go +0 -153
- package/templates/go/webview_go/.github/workflows/ci.yaml +0 -62
- package/templates/go/webview_go/CHANGELOG.md +0 -15
- package/templates/go/webview_go/LICENSE +0 -22
- package/templates/go/webview_go/README.md +0 -50
- package/templates/go/webview_go/examples/basic/main.go +0 -12
- package/templates/go/webview_go/examples/bind/main.go +0 -38
- package/templates/go/webview_go/glue.c +0 -36
- package/templates/go/webview_go/go.mod +0 -3
- package/templates/go/webview_go/go.sum +0 -0
- package/templates/go/webview_go/libs/mswebview2/LICENSE +0 -27
- package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +0 -23568
- package/templates/go/webview_go/libs/mswebview2/include/vendor.go +0 -2
- package/templates/go/webview_go/libs/mswebview2/vendor.go +0 -2
- package/templates/go/webview_go/libs/mswebview2/version.txt +0 -1
- package/templates/go/webview_go/libs/webview/LICENSE +0 -22
- package/templates/go/webview_go/libs/webview/include/vendor.go +0 -2
- package/templates/go/webview_go/libs/webview/include/webview.h +0 -3871
- package/templates/go/webview_go/libs/webview/vendor.go +0 -2
- package/templates/go/webview_go/libs/webview/version.txt +0 -1
- package/templates/go/webview_go/webview.cc +0 -1
- package/templates/go/webview_go/webview.go +0 -481
- package/templates/go/webview_go/webview_test.go +0 -50
package/templates/go/main.go
CHANGED
|
@@ -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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
17
|
-
procIsDebuggerPresent
|
|
18
|
-
procCheckRemoteDebuggerPresent
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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,58 +66,216 @@
|
|
|
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。
|
|
52
90
|
minimize: function () { return windowAction('minimize'); },
|
|
53
91
|
maximize: function () { return windowAction('maximize'); },
|
|
54
92
|
unmaximize: function () { return windowAction('unmaximize'); },
|
|
55
|
-
restore: function () { return windowAction('unmaximize'); },
|
|
56
93
|
toggleMaximize: function () { return windowAction('toggleMaximize'); },
|
|
57
|
-
close
|
|
94
|
+
// close(force):force=true 时绕过 interceptClose 拦截直接关闭。
|
|
95
|
+
close: function (force) { return windowAction('close', { force: !!force }); },
|
|
58
96
|
isMaximized: function () { return windowAction('isMaximized'); },
|
|
59
97
|
isFrameless: function () { return windowAction('isFrameless'); },
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 }); },
|
|
65
237
|
},
|
|
66
238
|
};
|
|
67
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
|
+
|
|
68
256
|
// 前端自绘按钮的便捷绑定:把按钮 DOM 接到窗口控制。
|
|
69
257
|
// freedom.window.bindButtons({ min: '#minBtn', max: '#maxBtn', close: '#closeBtn' })
|
|
70
258
|
freedom.window.bindButtons = function (sel) {
|
|
71
259
|
var q = function (s) { return typeof s === 'string' ? document.querySelector(s) : s; };
|
|
72
260
|
var min = q(sel && sel.min), max = q(sel && sel.max), close = q(sel && sel.close);
|
|
73
261
|
var self = this;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (min) min.addEventListener('click', function () {
|
|
77
|
-
self.minimize().catch(function (e) { console.warn('[freedom] minimize:', e); });
|
|
78
|
-
});
|
|
262
|
+
var ignoreErr = function () { /* 桥接未就绪(如窗口销毁中)时忽略本次操作 */ };
|
|
263
|
+
if (min) min.addEventListener('click', function () { self.minimize().catch(ignoreErr); });
|
|
79
264
|
if (max) max.addEventListener('click', function () {
|
|
80
265
|
self.isMaximized().then(function (m) {
|
|
81
|
-
if (m)
|
|
82
|
-
}).catch(
|
|
83
|
-
});
|
|
84
|
-
if (close) close.addEventListener('click', function () {
|
|
85
|
-
self.close().catch(function (e) { console.warn('[freedom] close:', e); });
|
|
266
|
+
if (m) self.unmaximize(); else self.maximize();
|
|
267
|
+
}).catch(ignoreErr);
|
|
86
268
|
});
|
|
269
|
+
if (close) close.addEventListener('click', function () { self.close().catch(ignoreErr); });
|
|
87
270
|
};
|
|
88
271
|
|
|
89
|
-
function windowAction(action) {
|
|
90
|
-
// __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 字符串。
|
|
91
275
|
if (typeof window.__freedom_window !== 'function') {
|
|
92
276
|
return Promise.reject(new Error('[freedom] 未检测到原生窗口控制桥接。'));
|
|
93
277
|
}
|
|
94
|
-
return window.__freedom_window(action);
|
|
278
|
+
return window.__freedom_window(action, JSON.stringify(args || {}));
|
|
95
279
|
}
|
|
96
280
|
|
|
97
281
|
Object.defineProperty(freedom, 'isDesktop', {
|
|
@@ -106,4 +290,18 @@
|
|
|
106
290
|
window.go = go;
|
|
107
291
|
|
|
108
292
|
window.freedom = freedom;
|
|
293
|
+
|
|
294
|
+
// H3:页面就绪回调。Go 侧 onReady 改为在 __freedom__ready 被调用时触发
|
|
295
|
+
//(此前在 SetHtml 前触发,期间 Emit 的初始化事件因 SDK 尚未建立而丢失)。
|
|
296
|
+
// DOMContentLoaded 后上报就绪;若脚本执行时 DOM 已加载完成则立即上报。
|
|
297
|
+
function signalReady() {
|
|
298
|
+
if (typeof window.__freedom__ready === 'function') {
|
|
299
|
+
try { window.__freedom__ready(); } catch (e) { /* 忽略 */ }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (document.readyState === 'loading') {
|
|
303
|
+
document.addEventListener('DOMContentLoaded', signalReady);
|
|
304
|
+
} else {
|
|
305
|
+
signalReady();
|
|
306
|
+
}
|
|
109
307
|
})();
|
|
@@ -2,20 +2,18 @@ package freedom
|
|
|
2
2
|
|
|
3
3
|
import _ "embed"
|
|
4
4
|
|
|
5
|
-
// jsSDK 是注入到每个前端页面的 Freedom 前端 SDK
|
|
6
|
-
//
|
|
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
|
-
//
|
|
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
|
+
}
|