bun-app-window 0.2.2 → 0.3.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/CHANGELOG.md +28 -1
- package/README.md +21 -1
- package/README.zh-CN.md +19 -1
- package/README.zh-TW.md +19 -1
- package/dist/libshell.dll +0 -0
- package/package.json +1 -1
- package/src/shell/types.ts +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-10-21
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **A page link could navigate the app away, permanently.** In a desktop shell there is no address bar, no back button, and no host API that can navigate the page back — so one mis-click on an ordinary `<a href="https://example.com">` replaced the application with that website, and the only recovery was to close and relaunch. The native layer could not stop it either: `NavigationStarting` was never subscribed, `NewWindowRequested` was never subscribed, and `g_origin` was never assigned.
|
|
14
|
+
|
|
15
|
+
This is worth describing precisely, because the failure was not a wrong decision — it was **241 lines of correct-looking code that had never been compiled**. `NavigationGuard`, `NewWindowGuard`, `open_external()`, `install_nav_guard()` and `uninstall_nav_guard()` were all present and heavily commented, but: `install_nav_guard()` had **no call site anywhere in the tree**; `SHELL_FLAG_ALLOW_EXTERNAL_NAV = 128` was declared in the C++ enum with **no parsing site**, and the TypeScript side had no corresponding constant at all, so a caller could not even express the intent; and `g_origin` carried a comment saying it was "computed by `shell_start_ex` from `g_url`" while never being assigned — which alone would have made `install_nav_guard()`'s first guard clause return early forever. Type checking, 532 test cases and the full gate were green throughout. The first `bun run build:native` produced six compile errors and settled it: `NavEventBase` existed to hold the refcount and `QueryInterface` plumbing, and **neither guard inherited it**.
|
|
16
|
+
|
|
17
|
+
Two independent guards now install before `webview_navigate` and uninstall before `webview_destroy` (after destroy, `core_webview()` returns `nullptr` and `remove_` is unreachable). Verified end-to-end on a real window: a cross-origin navigation is cancelled and handed to the system browser — which then actually loaded the URL — while a same-origin navigation in the same run went through untouched.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **`FLAG_ALLOW_EXTERNAL_NAV` (new, `128`) opts out of navigation interception** for applications that genuinely are browsers. This is the release's one breaking-in-effect change: without the flag, a cross-origin top-level navigation now opens the system browser instead of replacing the app. Nothing else about the flag's surrounding policy moved — `window.open` / `target=_blank` is still cancelled and opened externally **even with this flag**, because that is a capability boundary rather than a safety policy: the shell owns exactly one top-level window (the native side is a process-level singleton), so a second window would have no host API able to close, read or message it.
|
|
22
|
+
- Only `http:` / `https:` ever reach `ShellExecuteW`. `file:`, `javascript:`, `ms-*:` and custom schemes are cancelled silently and never touch a protocol handler, and `mailto:` / `tel:` are deliberately **not** on the list either — a dead `mailto:` link is preferable to handing a page an arbitrary protocol dispatcher.
|
|
23
|
+
- **An origin-less startup URL is no longer a free pass.** The old predicate returned "same origin" whenever `g_origin` was empty, which meant `file://`, `about:blank` and custom-scheme apps — the most common shape for a desktop shell — were the least protected. That branch now degrades to a *same-document* test: a navigation stays inside the window only if it matches the startup URL after stripping the `#fragment`. In-app hash routing keeps working; anything else is external.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `tests/nav-guard.test.ts` — eleven assertions, written against the **call-site and assignment-site** shapes rather than identifier presence, because `toContain("install_nav_guard")` passes on the definition alone and the definition was never the problem. It also pins the two `FLAG_*` tables together in both directions: every type constant must exist in the C++ enum **and** every C++ flag constant must exist in the type table. The forward assertion alone would not have caught this release's failure — the C++ side was written first and the TypeScript side was simply missing.
|
|
28
|
+
- `docs/pitfalls.md` 2.11 (declared-but-not-wired) and 2.12 (a probe's own criterion can lie), the latter recording two false criteria written during this work: a placeholder expression dressed as an assertion, and a "did the system browser load the page" check that cannot distinguish the browser from an in-webview navigation because both hit the same endpoint.
|
|
29
|
+
|
|
30
|
+
### Note
|
|
31
|
+
|
|
32
|
+
- The published tarball gains one exported constant (`FLAG_ALLOW_EXTERNAL_NAV`) and one `dist/libshell.dll` rebuild. Native exports are unchanged at 33 symbols.
|
|
33
|
+
- The behaviour change is observable without a flag: an app that was silently relying on external links navigating its own window will now see them open in the browser instead. That is the intent, and it is why this is a minor rather than a patch.
|
|
34
|
+
|
|
9
35
|
## [0.2.2] - 2026-10-21
|
|
10
36
|
|
|
11
37
|
### Fixed
|
|
@@ -195,7 +221,8 @@ First public release.
|
|
|
195
221
|
- Not yet verified in managed environments that enforce AppLocker, WDAC, or third-party EDR behaviour blocking.
|
|
196
222
|
- No commercially trusted code signing: only a self-signed certificate has been exercised end to end.
|
|
197
223
|
|
|
198
|
-
[Unreleased]: https://github.com/TiancongLx/bun-app-window/compare/v0.
|
|
224
|
+
[Unreleased]: https://github.com/TiancongLx/bun-app-window/compare/v0.3.0...HEAD
|
|
225
|
+
[0.3.0]: https://github.com/TiancongLx/bun-app-window/compare/v0.2.2...v0.3.0
|
|
199
226
|
[0.2.2]: https://github.com/TiancongLx/bun-app-window/compare/v0.2.1...v0.2.2
|
|
200
227
|
[0.2.1]: https://github.com/TiancongLx/bun-app-window/compare/v0.2.0...v0.2.1
|
|
201
228
|
[0.2.0]: https://github.com/TiancongLx/bun-app-window/compare/v0.1.0...v0.2.0
|
package/README.md
CHANGED
|
@@ -113,6 +113,26 @@ Combine via bitwise OR (`|`) and pass to `opts.flags`:
|
|
|
113
113
|
| `FLAG_DEFER_SHOW` | Creates the window hidden initially until `show()` is explicitly called |
|
|
114
114
|
| `FLAG_ACCEPT_DROP` | Enables native file and folder drag-and-drop from Windows Explorer |
|
|
115
115
|
| `FLAG_START_MAXIMIZED` | Opens the window directly in a maximized state without an intermediate normal-sized frame flash |
|
|
116
|
+
| `FLAG_ALLOW_EXTERNAL_NAV` | Opts out of external-navigation interception — for apps that genuinely are browsers |
|
|
117
|
+
|
|
118
|
+
### External navigation is blocked by default
|
|
119
|
+
|
|
120
|
+
Without `FLAG_ALLOW_EXTERNAL_NAV`, a cross-origin link can no longer navigate the app away.
|
|
121
|
+
The shell cancels the navigation and hands the URL to the system browser instead:
|
|
122
|
+
|
|
123
|
+
- **Same origin** (or, for `file://` / other origin-less startup URLs, the same document
|
|
124
|
+
ignoring the `#fragment`) stays inside the window. In-app routing keeps working.
|
|
125
|
+
- **Cross origin** is cancelled and opened externally — but only for `http:` / `https:`.
|
|
126
|
+
`file:`, `javascript:`, `ms-*:` and custom schemes are cancelled silently and never
|
|
127
|
+
reach a protocol handler. `mailto:` / `tel:` are deliberately not allowed.
|
|
128
|
+
- **`window.open` / `target=_blank`** is always cancelled and opened externally, even with
|
|
129
|
+
`FLAG_ALLOW_EXTERNAL_NAV`. That is a capability boundary, not a policy: the shell owns a
|
|
130
|
+
single top-level window (native side is a process-level singleton), so a second window
|
|
131
|
+
would have no host API to manage it.
|
|
132
|
+
|
|
133
|
+
A desktop shell has no address bar and no back button, and no host API can navigate the page
|
|
134
|
+
back — so a single mis-click would otherwise replace the app with that website. That is why
|
|
135
|
+
the default picks the safe side.
|
|
116
136
|
|
|
117
137
|
## Subpath Exports
|
|
118
138
|
|
|
@@ -407,7 +427,7 @@ The underlying commands remain available directly:
|
|
|
407
427
|
|
|
408
428
|
```bash
|
|
409
429
|
bun check # Run TypeScript type checker
|
|
410
|
-
bun test # Run automated test suite (tests
|
|
430
|
+
bun test # Run automated test suite (tests/)
|
|
411
431
|
bun run demo # Launch interactive self-test demo (requires desktop session)
|
|
412
432
|
```
|
|
413
433
|
|
package/README.zh-CN.md
CHANGED
|
@@ -113,6 +113,24 @@ shell.setTitle("Doing work…");
|
|
|
113
113
|
| `FLAG_DEFER_SHOW` | 创建窗口后保持隐藏状态,等待显式调用 `show()` |
|
|
114
114
|
| `FLAG_ACCEPT_DROP` | 允许窗口接收外部文件或文件夹拖放事件 |
|
|
115
115
|
| `FLAG_START_MAXIMIZED` | 窗口首次显示即处于最大化状态,避免从普通尺寸跳变到最大化的中间闪烁帧 |
|
|
116
|
+
| `FLAG_ALLOW_EXTERNAL_NAV` | 放弃外部导航拦截——用于本身就是浏览器的应用 |
|
|
117
|
+
|
|
118
|
+
### 外部导航默认被拦截
|
|
119
|
+
|
|
120
|
+
不给 `FLAG_ALLOW_EXTERNAL_NAV` 时,一个跨源链接再也无法把应用导航走。壳会取消这次导航,
|
|
121
|
+
把地址交给系统默认浏览器:
|
|
122
|
+
|
|
123
|
+
- **同源**(启动地址是 `file://` 等无 origin 的形态时,改为判断「同文档」,即剥掉
|
|
124
|
+
`#fragment` 后与启动地址逐字相同)留在窗口内导航,应用自己的路由不受影响。
|
|
125
|
+
- **跨源**会取消导航并交给系统浏览器——但只有 `http:` / `https:`。`file:`、
|
|
126
|
+
`javascript:`、`ms-*:` 以及自定义 scheme 一律静默取消,绝不触达协议处理器;
|
|
127
|
+
`mailto:` / `tel:` 也**不在**放行之列。
|
|
128
|
+
- **`window.open` / `target=_blank`** 始终被拦下并外开,即使给了
|
|
129
|
+
`FLAG_ALLOW_EXTERNAL_NAV` 也一样。那是能力边界而非策略:壳只持有一个顶层窗口
|
|
130
|
+
(原生层是进程级单例),多开出来的窗口没有任何宿主 API 能管它。
|
|
131
|
+
|
|
132
|
+
桌面壳没有地址栏、没有后退按钮,也没有任何宿主 API 能把页面导航回来——所以一次误点
|
|
133
|
+
外链就等于应用被换成了那个网站。这就是默认值选安全一侧的原因。
|
|
116
134
|
|
|
117
135
|
## 子路径模块
|
|
118
136
|
|
|
@@ -405,7 +423,7 @@ bun run scripts/gate.ts --clean # 同上,但在 HEAD 的干净副本里
|
|
|
405
423
|
|
|
406
424
|
```bash
|
|
407
425
|
bun check # 执行 TypeScript 类型检查
|
|
408
|
-
bun test # 运行自动化测试套件(位于 tests
|
|
426
|
+
bun test # 运行自动化测试套件(位于 tests/)
|
|
409
427
|
bun run demo # 启动交互式自检示例程序(需在桌面会话下运行)
|
|
410
428
|
```
|
|
411
429
|
|
package/README.zh-TW.md
CHANGED
|
@@ -113,6 +113,24 @@ shell.setTitle("Doing work…");
|
|
|
113
113
|
| `FLAG_DEFER_SHOW` | 建立視窗後保持隱藏狀態,等待明確呼叫 `show()` |
|
|
114
114
|
| `FLAG_ACCEPT_DROP` | 允許視窗接收外部檔案或資料夾拖放事件 |
|
|
115
115
|
| `FLAG_START_MAXIMIZED` | 視窗首次顯示即處於最大化狀態,避免從一般尺寸跳變至最大化的中間閃爍畫格 |
|
|
116
|
+
| `FLAG_ALLOW_EXTERNAL_NAV` | 放棄外部導覽攔截——用於本身就是瀏覽器的應用程式 |
|
|
117
|
+
|
|
118
|
+
### 外部導覽預設被攔截
|
|
119
|
+
|
|
120
|
+
未提供 `FLAG_ALLOW_EXTERNAL_NAV` 時,一個跨來源連結再也無法把應用程式導覽走。殼層會取消該次導覽,
|
|
121
|
+
把位址交給系統預設瀏覽器:
|
|
122
|
+
|
|
123
|
+
- **同來源**(啟動位址是 `file://` 等無 origin 的形態時,改為判斷「同文件」,即剝除
|
|
124
|
+
`#fragment` 後與啟動位址逐字相同)留在視窗內導覽,應用程式自身的路由不受影響。
|
|
125
|
+
- **跨來源**會取消導覽並交給系統瀏覽器——但僅限 `http:` / `https:`。`file:`、
|
|
126
|
+
`javascript:`、`ms-*:` 以及自訂 scheme 一律靜默取消,絕不觸及協定處理常式;
|
|
127
|
+
`mailto:` / `tel:` 也**不在**放行清單內。
|
|
128
|
+
- **`window.open` / `target=_blank`** 一律被攔下並外開,即使提供了
|
|
129
|
+
`FLAG_ALLOW_EXTERNAL_NAV` 亦然。那是能力邊界而非策略:殼層僅持有一個頂層視窗
|
|
130
|
+
(原生層是處理序級單例),多開出來的視窗沒有任何宿主 API 能管理它。
|
|
131
|
+
|
|
132
|
+
桌面殼層沒有網址列、沒有上一頁按鈕,也沒有任何宿主 API 能把頁面導覽回來——所以一次誤點
|
|
133
|
+
外部連結就等於應用程式被換成了那個網站。這就是預設值選擇安全一側的原因。
|
|
116
134
|
|
|
117
135
|
## 子路徑模組
|
|
118
136
|
|
|
@@ -405,7 +423,7 @@ bun run scripts/gate.ts --clean # 同上,但在 HEAD 的乾淨副本內
|
|
|
405
423
|
|
|
406
424
|
```bash
|
|
407
425
|
bun check # 執行 TypeScript 型別檢查
|
|
408
|
-
bun test # 執行自動化測試套件(位於 tests
|
|
426
|
+
bun test # 執行自動化測試套件(位於 tests/)
|
|
409
427
|
bun run demo # 啟動互動式自我檢測範例程式(需於桌面工作階段下執行)
|
|
410
428
|
```
|
|
411
429
|
|
package/dist/libshell.dll
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bun-app-window",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Native WebView2 window shell for Bun — frameless or system-decorated windows, DPI-aware geometry, typed IPC, system-accurate title bar",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
package/src/shell/types.ts
CHANGED
|
@@ -42,6 +42,24 @@ export const FLAG_ACCEPT_DROP = 32;
|
|
|
42
42
|
* 与 `FLAG_CENTER` 同时给时以本项为准(最大化会覆盖位置)。
|
|
43
43
|
*/
|
|
44
44
|
export const FLAG_START_MAXIMIZED = 64;
|
|
45
|
+
/**
|
|
46
|
+
* 允许页面把顶层框架导航到任意 http/https 地址,即**放弃外部导航拦截**。
|
|
47
|
+
*
|
|
48
|
+
* 默认(不给本位)时,壳会拦下跨源导航并改交系统默认浏览器:桌面应用没有地址栏、
|
|
49
|
+
* 没有后退按钮,也没有宿主 API 能把页面拉回来,一次误点外链就等于应用被换成了那个
|
|
50
|
+
* 网站。这是默认值应该选安全一侧的典型场景。
|
|
51
|
+
*
|
|
52
|
+
* 什么时候需要本位:应用本身就是浏览器(多标签、内置地址栏、能自己导航回来)。
|
|
53
|
+
*
|
|
54
|
+
* 边界(两条,别指望它能关掉更多):
|
|
55
|
+
*
|
|
56
|
+
* - **只影响顶层框架导航**。`window.open` / `target=_blank` 仍然被拦下并外开,
|
|
57
|
+
* 因为壳只支持一个顶层窗口(原生层是进程级单例),开出来的新窗口没有任何宿主
|
|
58
|
+
* API 能管它。这是能力边界,不是安全策略。
|
|
59
|
+
* - **不放宽 scheme 白名单**。交给系统浏览器的只有 http/https;`file:`、
|
|
60
|
+
* `javascript:`、`ms-*:` 以及自定义 scheme 一律静默取消,与本位无关。
|
|
61
|
+
*/
|
|
62
|
+
export const FLAG_ALLOW_EXTERNAL_NAV = 128;
|
|
45
63
|
|
|
46
64
|
/**
|
|
47
65
|
* 关闭 WebView2 的浏览器行为(`ShellOptions.browserUi` 使用)。
|