fix(t72): portal 下載頁依作業系統分流(原本寫死「下載 Mac 版」)

leo 07-27 交辦第①件的一部分。B 類(改 Arcrun 本體 UI)→ **待 leo 審**,未合併。

問題:進站完成安裝清單只給「下載 Mac 版」,Windows 用戶按下去拿到 .app 的 zip,
打不開也不知道為什麼。

改動(console-ui/public/portal/index.html):
- 新增 detectOS():優先 navigator.userAgentData.platform(不受 UA 凍結影響),
  退回 platform/userAgent;iPadOS 自稱 Macintosh 用 maxTouchPoints 排除。
  **認不出來就不猜**,兩個連結都給(同「按了連到錯的機制=不友善」判準)。
- 主按鈕依 OS 換字(下載 Windows 版/Mac 版)+另一系統的小字連結。
- 首開提示依 OS 換:Windows=SmartScreen「其他資訊→仍要執行」白話說明;
  Mac=原本的右鍵→打開。
- Windows zip **尚未上鏡像**(交叉編譯已通、真機未驗)→ 沒設 config.daemonDownloadWin
  時誠實顯示「Windows 版封測驗證中」,不放 404 連結;Windows 用戶仍可改抓 Mac 版或手動打勾。
- config.js 補註解說明 daemonDownload / daemonDownloadWin 兩個 key。

驗證:console-ui/test/portal-os-split.test.mjs(自帶,node 直接跑)——
8 種 navigator 情境全過(Win Chrome/舊 Firefox、mac Safari/Chrome、iPad、Linux、Android、空值),
並印出三種 OS 各自的實際文案;index.html 主 script 區塊 node --check 語法通過。
⚠️ 未在真瀏覽器點過(沙箱無 portal session),故標待審不標完成。
This commit is contained in:
cloud-worker
2026-07-27 09:57:52 +00:00
parent 139d4c5ed1
commit c93ed6f0f7
3 changed files with 102 additions and 5 deletions
+54
View File
@@ -0,0 +1,54 @@
// t72 下載頁 OS 分流單測(2026-07-27):8 種 navigator 情境 → 分流結果。
// 用法:node console-ui/test/portal-os-split.test.mjs
// 直接從 index.html 抽最大的 <script> 區塊,測 t72 新增的四個分流函式。
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const here = path.dirname(fileURLToPath(import.meta.url));
const html = fs.readFileSync(path.join(here, '..', 'public', 'portal', 'index.html'), 'utf8');
const src = html.match(/<script>([\s\S]*?)<\/script>/g)
.map((b) => b.replace(/^<script>|<\/script>$/g, ''))
.sort((a, b) => b.length - a.length)[0];
const pick = (name) => {
const i = src.indexOf('function ' + name + '(');
if (i < 0) throw new Error('找不到 ' + name);
let depth = 0, j = src.indexOf('{', i);
for (let k = j; k < src.length; k++) {
if (src[k] === '{') depth++;
else if (src[k] === '}') { depth--; if (!depth) return src.slice(i, k + 1); }
}
};
const code = ['detectOS','osLabel','firstOpenHint','otherOsLine'].map(pick).join('\n');
const esc = (s) => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;');
const make = (nav) => {
const fn = new Function('navigator','esc', code + '; return {detectOS,osLabel,firstOpenHint,otherOsLine};');
return fn(nav, esc);
};
const MAC='https://x/mac.zip', WIN='https://x/win.zip';
const cases = [
['Windows 11 Chrome', {userAgentData:{platform:'Windows'},platform:'Win32',userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',maxTouchPoints:0}, 'win'],
['Windows 舊 Firefox(無 userAgentData', {platform:'Win32',userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/128',maxTouchPoints:0}, 'win'],
['macOS Safari', {platform:'MacIntel',userAgent:'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605',maxTouchPoints:0}, 'mac'],
['macOS Chrome (UA-CH)', {userAgentData:{platform:'macOS'},platform:'MacIntel',userAgent:'Mozilla/5.0 (Macintosh) Chrome/141',maxTouchPoints:0}, 'mac'],
['iPad(自稱 Macintosh,有觸控)', {platform:'MacIntel',userAgent:'Mozilla/5.0 (Macintosh; Intel Mac OS X) Safari',maxTouchPoints:5}, 'unknown'],
['Linux', {platform:'Linux x86_64',userAgent:'Mozilla/5.0 (X11; Linux x86_64)',maxTouchPoints:0}, 'unknown'],
['Android', {platform:'Linux armv8l',userAgent:'Mozilla/5.0 (Linux; Android 14)',maxTouchPoints:5}, 'unknown'],
['什麼都沒有', {maxTouchPoints:0}, 'unknown'],
];
let fail = 0;
for (const [name, nav, want] of cases) {
const m = make(nav);
const got = m.detectOS();
const ok = got === want;
if (!ok) fail++;
console.log(`${ok?'✅':'❌'} ${name.padEnd(34)}${got} (期望 ${want})`);
}
console.log('\n--- 各情境實際顯示的文字 ---');
for (const os of ['win','mac','unknown']) {
const m = make({});
console.log(`\n[${os}] 主按鈕:下載 ${m.osLabel(os)}`);
console.log(` 首開提示:${m.firstOpenHint(os)}`);
console.log(` 另一個系統(Win zip 已上架):${m.otherOsLine(os, MAC, WIN) || '(不顯示)'}`);
console.log(` 另一個系統(Win zip 未上架=現況):${m.otherOsLine(os, MAC, '') || '(不顯示)'}`);
}
process.exit(fail ? 1 : 0);