fix(portal/t72): 下載頁 OS 分流——Windows 客戶不再拿到 Mac 的 .app
leo 07-27:「客戶是用 windows 的」。此前四處寫死「下載 Mac 版」,
Windows 用戶按下去拿到 .app=按了連到錯的東西(leo 判準:那不算友善)。
改法(施工圖 rag-wave1/windows-build-and-os-split.md §3):
- daemonPick() 依 UA 判 OS,四處共用;判不出來=兩個都給,不替用戶猜
- 判對了也附「不是這個系統?」另一版連結(UA 會判錯,用戶要有路走)
- 擋關話術跟著 OS 走:Mac=右鍵打開/Windows=更多資訊→仍要執行
- daemonBase 新 key,保留 daemonDownload 舊 key 相容(由檔名推目錄)
- 一律走 raw:Mac zip 21MB > jsDelivr 20MB 上限(實測回 File size exceeded)
測試 os-split.test.mjs 10/10:
⚠️ 測試抓到真 bug——iPhone 的 UA 含 'Mac OS X' 會被判成 Mac,
讓手機用戶下載裝不起來的桌面 app。已加 isMobile 排除,手機落到「兩個都給」。
未驗:真 Windows 機器的實際下載與安裝行為(需真機)。
This commit is contained in:
@@ -667,13 +667,70 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
|
||||
})
|
||||
.catch(function (e) { $('login-submit').disabled = false; $('login-status').textContent = friendlyErr(e); });
|
||||
}
|
||||
// t72 OS 分流(2026-07-27,leo:「客戶是用 windows 的」)——在此之前四處寫死「下載 Mac 版」,
|
||||
// Windows 客戶按下去會拿到 Mac 的 .app=按了連到錯的東西(leo 判準:那不算友善)。
|
||||
// 判不出 OS 時**兩個都給**,不替用戶猜;且無論判成哪個,頁面都留「不是這個系統?」的另一版連結
|
||||
// ——UA 會判錯,判錯時用戶要有路可走(施工圖 §3 注意事項 1、3)。
|
||||
var DAEMON_BASE_DEFAULT = 'https://raw.githubusercontent.com/youlinhsieh/arcrun-rag-bundles/main/daemon/';
|
||||
var DAEMON_MAC = 'ArcrunRAG-mac-unsigned.zip';
|
||||
var DAEMON_WIN = 'ArcrunRAG-win-unsigned.zip';
|
||||
// Mac 那顆 21MB > jsDelivr 單檔 20MB 上限(實測回 "File size exceeded...")→ 一律走 raw;
|
||||
// Windows 13MB 雖在限內,同走 raw 保持單一來源、少一個會壞的地方。
|
||||
function daemonBase() {
|
||||
var cfg = (window.ARCRUN_CONFIG || {});
|
||||
if (cfg.daemonBase) return String(cfg.daemonBase).replace(/\/?$/, '/');
|
||||
// 舊 key 相容(施工圖 §3 注意事項 2):daemonDownload 是「單一檔案網址」,
|
||||
// 有設就尊重它當 Mac 版網址,並由它推出目錄給 Windows 版用。
|
||||
if (cfg.daemonDownload) {
|
||||
var s = String(cfg.daemonDownload);
|
||||
return s.slice(0, s.lastIndexOf('/') + 1) || DAEMON_BASE_DEFAULT;
|
||||
}
|
||||
return DAEMON_BASE_DEFAULT;
|
||||
}
|
||||
function daemonPick() {
|
||||
var base = daemonBase();
|
||||
var ua = navigator.userAgent || '';
|
||||
// 手機/平板先排除:iOS 的 UA 含 "Mac OS X",不排除會把 iPhone 判成 Mac,
|
||||
// 讓手機用戶下載一個裝不起來的桌面 app(測到才發現,2026-07-27)。
|
||||
// 手機=判不出桌面 OS → 落到「兩個都給」,用戶回桌機再選。
|
||||
var isMobile = /iPhone|iPad|iPod|Android|Mobile/i.test(ua);
|
||||
var isWin = !isMobile && /Windows NT/i.test(ua);
|
||||
var isMac = !isMobile && /Macintosh|Mac OS X/i.test(ua) && !/Windows/i.test(ua);
|
||||
var mac = { os: 'mac', label: '下載 Mac 版', url: base + DAEMON_MAC };
|
||||
var win = { os: 'win', label: '下載 Windows 版', url: base + DAEMON_WIN };
|
||||
if (isWin) return { pick: win, other: mac, sure: true };
|
||||
if (isMac) return { pick: mac, other: win, sure: true };
|
||||
return { pick: null, other: null, sure: false, mac: mac, win: win };
|
||||
}
|
||||
// 封測期第一次開啟的擋關提示(未簽章)——Mac/Windows 攔法不同,話術也不同。
|
||||
function daemonHint(os) {
|
||||
if (os === 'win') return '(封測版未簽章,Windows 第一次會跳藍色視窗擋下來——點「更多資訊」→「仍要執行」就好)';
|
||||
return '(封測版未簽章,第一次請右鍵→打開)';
|
||||
}
|
||||
// 07-27:設定頁的常駐入口(下載小幫手/換 AI 金鑰)——與一次性卡片同一組 API
|
||||
(function () {
|
||||
var dl = $('st-daemon-dl');
|
||||
if (dl) {
|
||||
var u = (window.ARCRUN_CONFIG && window.ARCRUN_CONFIG.daemonDownload)
|
||||
|| 'https://raw.githubusercontent.com/youlinhsieh/arcrun-rag-bundles/main/daemon/ArcrunRAG-mac-unsigned.zip';
|
||||
dl.setAttribute('href', u);
|
||||
var d = daemonPick();
|
||||
if (d.sure) {
|
||||
dl.setAttribute('href', d.pick.url);
|
||||
dl.textContent = d.pick.label;
|
||||
// 判對了也要留另一版的路(UA 會判錯)
|
||||
var alt = document.createElement('span');
|
||||
alt.className = 'muted';
|
||||
alt.style.cssText = 'font-size:12.5px;margin-left:8px';
|
||||
alt.innerHTML = '不是這個系統?<a href="' + d.other.url + '">' + d.other.label + '</a>';
|
||||
if (dl.parentNode) dl.parentNode.insertBefore(alt, dl.nextSibling);
|
||||
} else {
|
||||
// 判不出來=兩個都給,不預設 Mac
|
||||
dl.setAttribute('href', d.mac.url);
|
||||
dl.textContent = d.mac.label;
|
||||
var both = document.createElement('span');
|
||||
both.className = 'muted';
|
||||
both.style.cssText = 'font-size:12.5px;margin-left:8px';
|
||||
both.innerHTML = '或 <a href="' + d.win.url + '">' + d.win.label + '</a>';
|
||||
if (dl.parentNode) dl.parentNode.insertBefore(both, dl.nextSibling);
|
||||
}
|
||||
}
|
||||
var kb = $('st-key-save');
|
||||
if (kb) kb.addEventListener('click', function () {
|
||||
@@ -719,7 +776,8 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
|
||||
var s = setupSteps();
|
||||
if (s.daemon && s.key) return; // t54 起只剩兩件:設定改由小幫手輸入帳密自取,不再下載檔案
|
||||
var cfg = window.ARCRUN_CONFIG || {};
|
||||
var daemonUrl = cfg.daemonDownload || 'https://raw.githubusercontent.com/youlinhsieh/arcrun-rag-bundles/main/daemon/ArcrunRAG-mac-unsigned.zip';
|
||||
var dpick = daemonPick(); // t72 OS 分流(同一組判定,見上面 daemonPick)
|
||||
var daemonUrl = dpick.sure ? dpick.pick.url : dpick.mac.url;
|
||||
var row = function (done, id, html) {
|
||||
return '<div style="display:flex;gap:10px;align-items:baseline;margin-top:10px">'
|
||||
+ '<span>' + (done ? '✅' : '⬜') + '</span><div style="flex:1">' + html + '</div></div>';
|
||||
@@ -731,11 +789,20 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
|
||||
+ row(s.daemon, 'daemon',
|
||||
'<b>下載同步小幫手</b>(把資料夾變成知識庫)<br>'
|
||||
+ (daemonUrl
|
||||
? '<a href="' + esc(daemonUrl) + '" id="sc-daemon">下載 Mac 版</a>'
|
||||
+ '<span class="muted" style="font-size:12.5px">(封測版未簽章,第一次請右鍵→打開)</span>'
|
||||
// t72:按鈕字樣+擋關話術都跟著 OS 走;並且一律附「另一個系統」的連結
|
||||
//(UA 判錯時用戶要有路走;判不出來時=兩個平等並列,不預設 Mac)
|
||||
? '<a href="' + esc(daemonUrl) + '" id="sc-daemon">'
|
||||
+ esc(dpick.sure ? dpick.pick.label : dpick.mac.label) + '</a>'
|
||||
+ '<span class="muted" style="font-size:12.5px">'
|
||||
+ esc(daemonHint(dpick.sure ? dpick.pick.os : 'mac')) + '</span>'
|
||||
+ '<div class="muted" style="font-size:12.5px">'
|
||||
+ (dpick.sure
|
||||
? '不是這個系統?<a href="' + esc(dpick.other.url) + '">' + esc(dpick.other.label) + '</a>'
|
||||
: '或 <a href="' + esc(dpick.win.url) + '">' + esc(dpick.win.label) + '</a>')
|
||||
+ '</div>'
|
||||
// 誠實降級(leo 判準:按了連到錯的機制=不友善):沒有可用下載點時
|
||||
// 不放死連結,說明現況並讓這步可手動打勾,不卡住清單。
|
||||
: '<span class="muted" style="font-size:13px">封測期由我們直接寄給你安裝檔(Mac 版);'
|
||||
: '<span class="muted" style="font-size:13px">封測期由我們直接寄給你安裝檔;'
|
||||
+ '收到後回來按 <a href="#" id="sc-daemon">我已經裝好了</a>。</span>')
|
||||
// t54(leo:「最好的就是把它的帳密直接輸入」):設定不再是一個要下載的檔案——
|
||||
// 小幫手第一次開啟會問網址+帳密,自己去換設定。
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import fs from 'node:fs';
|
||||
const html = fs.readFileSync(new URL('./index.html', import.meta.url).pathname,'utf8');
|
||||
// 抽出 daemonPick 相關函式(從 DAEMON_BASE_DEFAULT 到 daemonHint 結尾)
|
||||
const start = html.indexOf('var DAEMON_BASE_DEFAULT');
|
||||
const endMark = "return '(封測版未簽章,第一次請右鍵→打開)';\n }";
|
||||
const end = html.indexOf(endMark) + endMark.length;
|
||||
if (start < 0 || end < start) throw new Error('抽不到函式區塊');
|
||||
const src = html.slice(start, end);
|
||||
|
||||
const cases = [
|
||||
['Windows', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36'],
|
||||
['Mac', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15'],
|
||||
['iPhone', 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Safari/604.1'],
|
||||
['Linux', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120 Safari/537.36'],
|
||||
];
|
||||
let pass=0, fail=0;
|
||||
const chk=(l,c,extra='')=>{ if(c){console.log('PASS:',l);pass++;} else {console.log('FAIL:',l,extra);fail++;} };
|
||||
|
||||
for (const [name, ua] of cases) {
|
||||
const fn = new Function('navigator','window', src + '; return {daemonPick:daemonPick, daemonHint:daemonHint, daemonBase:daemonBase};');
|
||||
const api = fn({userAgent: ua}, {});
|
||||
const d = api.daemonPick();
|
||||
const label = d.sure ? d.pick.label : '(兩個都給)';
|
||||
const url = d.sure ? d.pick.url : d.mac.url + ' + ' + d.win.url;
|
||||
console.log(`\n[${name}] sure=${d.sure} → ${label}`);
|
||||
console.log(` url: ${url}`);
|
||||
if (name==='Windows') {
|
||||
chk('Windows 給 win zip', d.sure && d.pick.url.endsWith('ArcrunRAG-win-unsigned.zip'), d.pick&&d.pick.url);
|
||||
chk('Windows 另一版是 Mac', d.other && d.other.url.endsWith('mac-unsigned.zip'));
|
||||
chk('Windows 話術提 藍色視窗', api.daemonHint('win').includes('仍要執行'));
|
||||
}
|
||||
if (name==='Mac') {
|
||||
chk('Mac 給 mac zip', d.sure && d.pick.url.endsWith('ArcrunRAG-mac-unsigned.zip'));
|
||||
chk('Mac 另一版是 Windows', d.other && d.other.url.endsWith('win-unsigned.zip'));
|
||||
chk('Mac 話術提 右鍵打開', api.daemonHint('mac').includes('右鍵'));
|
||||
}
|
||||
if (name==='iPhone' || name==='Linux') {
|
||||
// iPhone 含 "Mac OS X" 但不是桌機 Mac;Linux 兩者皆非 → 都該落在「不確定=兩個都給」
|
||||
if (name==='Linux') chk('Linux 判不出來→兩個都給', d.sure===false);
|
||||
if (name==='iPhone') chk('iPhone 不該被判成 Mac(手機→兩個都給)', d.sure===false, 'sure='+d.sure);
|
||||
}
|
||||
}
|
||||
// 舊 key 相容
|
||||
const fn2 = new Function('navigator','window', src + '; return daemonBase();');
|
||||
console.log('\n[相容] daemonDownload 舊 key →', fn2({userAgent:''},{ARCRUN_CONFIG:{daemonDownload:'https://x.dev/d/ArcrunRAG-mac-unsigned.zip'}}));
|
||||
chk('舊 key 推得出目錄', fn2({userAgent:''},{ARCRUN_CONFIG:{daemonDownload:'https://x.dev/d/ArcrunRAG-mac-unsigned.zip'}})==='https://x.dev/d/');
|
||||
chk('daemonBase 新 key 優先', fn2({userAgent:''},{ARCRUN_CONFIG:{daemonBase:'https://y.dev/z'}})==='https://y.dev/z/');
|
||||
console.log(`\n=== ${pass} passed, ${fail} failed ===`);
|
||||
process.exit(fail?1:0);
|
||||
Reference in New Issue
Block a user