cicy-desktop 2.1.328 → 2.1.330
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/package.json +1 -1
- package/src/app-updater.js +78 -16
- package/src/backends/homepage-preload.js +8 -0
- package/src/backends/homepage-react/assets/index-CppodgBn.js +377 -0
- package/src/backends/homepage-react/index.html +1 -1
- package/src/main.js +11 -0
- package/src/sidecar/host-mihomo.js +13 -4
- package/src/tabbrowser/panel-menu-store.js +87 -0
- package/src/tabbrowser/panel-menu.js +13 -18
- package/src/tabbrowser/panel-presets.js +28 -4
- package/src/tools/index.js +1 -0
- package/src/tools/panel-menu-tools.js +80 -0
- package/test/app-updater-github-fallback.test.js +81 -0
- package/test/facebook-matrix.test.js +10 -2
- package/test/panel-menu-config.test.js +96 -0
- package/workers/render/src/App.jsx +154 -0
- package/src/backends/homepage-react/assets/index-Cj1zsmOl.js +0 -377
package/package.json
CHANGED
package/src/app-updater.js
CHANGED
|
@@ -21,7 +21,7 @@ const os = require("os");
|
|
|
21
21
|
const { readGlobalConfig, updateGlobalConfig } = require("./utils/global-json");
|
|
22
22
|
const https = require("https");
|
|
23
23
|
const log = require("electron-log");
|
|
24
|
-
const { R2_RELEASES_BASE } = require("./sidecar/mirrors");
|
|
24
|
+
const { R2_RELEASES_BASE, buildUrlList } = require("./sidecar/mirrors");
|
|
25
25
|
|
|
26
26
|
// ── 版本比较:>0 a 新于 b ─────────────────────────────────────────────────────
|
|
27
27
|
function cmpVer(a, b) {
|
|
@@ -74,25 +74,76 @@ function headOk(url, redirects = 0) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// ── 最新版本号 ────────────────────────────────────────────────────────────────
|
|
77
|
+
// R2 优先(win/mac/linux 各自指针,两条 CI 版本可能不同步,所以各读各的),GitHub 兜底。
|
|
78
|
+
//
|
|
79
|
+
// 原先这里是「一律读 OSS,彻底不碰 GitHub」,理由是仓库可能私有。代价是 R2 一旦不可达,
|
|
80
|
+
// 自更新就整条断掉 —— 而 fleet 里确实有一批 Windows 节点连 r2.deepfetch.de5.net 直接
|
|
81
|
+
// ECONNRESET(GitHub 反而 ~300ms 可达),它们因此永远停在旧版、只能人工推包。
|
|
82
|
+
// 现在 R2 仍是首选(仓库若再转私有,这条路不受影响),失败才回退 GitHub Release;
|
|
83
|
+
// 两边都不通才算真失败。
|
|
84
|
+
const GH_REPO = "cicy-ai/cicy-desktop";
|
|
85
|
+
|
|
86
|
+
function fetchLatestVersionFromGitHub() {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const req = https.get(
|
|
89
|
+
`https://api.github.com/repos/${GH_REPO}/releases/latest`,
|
|
90
|
+
{ headers: { "User-Agent": "cicy-desktop-updater", Accept: "application/vnd.github+json" }, timeout: 10000 },
|
|
91
|
+
(res) => {
|
|
92
|
+
if (res.statusCode !== 200) { res.resume(); return reject(new Error(`GitHub HTTP ${res.statusCode}`)); }
|
|
93
|
+
let body = "";
|
|
94
|
+
res.setEncoding("utf8");
|
|
95
|
+
res.on("data", (d) => (body += d));
|
|
96
|
+
res.on("end", () => {
|
|
97
|
+
try {
|
|
98
|
+
const tag = String((JSON.parse(body) || {}).tag_name || "").replace(/^v/, "");
|
|
99
|
+
tag ? resolve(tag) : reject(new Error("no tag_name"));
|
|
100
|
+
} catch (e) { reject(e); }
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
req.on("error", reject);
|
|
105
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
77
109
|
async function fetchLatestVersion() {
|
|
78
|
-
// 版本清单**一律读 OSS**(win/mac/linux 各自指针),与网络判定无关 —— 彻底不碰 GitHub,
|
|
79
|
-
// 这样 cicy-desktop 仓库私有也不影响更新检查。win/mac 是两条独立 CI、版本可能不同步,读错
|
|
80
|
-
// 平台会 404,所以各读各的指针文件。
|
|
81
110
|
const file = process.platform === "win32" ? "win-latest-version.txt"
|
|
82
111
|
: process.platform === "darwin" ? "mac-latest-version.txt"
|
|
83
112
|
: "linux-latest-version.txt";
|
|
84
|
-
|
|
113
|
+
try {
|
|
114
|
+
return (await getText(`${R2_RELEASES_BASE}/${file}`)).trim().replace(/^v/, "");
|
|
115
|
+
} catch (e) {
|
|
116
|
+
log.warn(`[app-updater] R2 版本指针不可达(${e.message}),回退 GitHub Release`);
|
|
117
|
+
return await fetchLatestVersionFromGitHub();
|
|
118
|
+
}
|
|
85
119
|
}
|
|
86
120
|
|
|
87
|
-
// ──
|
|
121
|
+
// ── 安装包来源(有序候选)+ 本地文件名 ──────────────────────────────────────
|
|
122
|
+
// 返回 { file, urls } —— urls 是按优先级排好的候选:R2 → GitHub 直连 → ghproxy 镜像。
|
|
123
|
+
// 调用方依次尝试,第一个能下的算数(见 check() 的 HEAD 探测和 downloadUpdate 的重试)。
|
|
124
|
+
//
|
|
125
|
+
// 注意 Windows 两边**文件名不同**:R2 上是 cicy-desktop-<ver>.exe(CI 传的版本化副本),
|
|
126
|
+
// GitHub Release 里是 electron-builder 产出的 CiCy-Desktop-Setup-<ver>.exe。
|
|
127
|
+
// mac 的 pkg 和 linux 的 AppImage 两边同名。`file` 只是本地落盘名,保持原样不变。
|
|
88
128
|
function assetFor(version) {
|
|
89
129
|
const plat = process.platform;
|
|
90
130
|
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
91
|
-
// 三端安装包**全部从 OSS 拉**,与网络无关 —— 不再拼 GitHub 下载 URL,仓库私有也能更新。
|
|
92
131
|
const file = plat === "win32" ? `cicy-desktop-${version}.exe`
|
|
93
132
|
: plat === "darwin" ? `cicy-desktop-${version}-${arch}.pkg`
|
|
94
133
|
: `CiCy-Desktop-${version}.AppImage`;
|
|
95
|
-
|
|
134
|
+
const ghName = plat === "win32" ? `CiCy-Desktop-Setup-${version}.exe` : file;
|
|
135
|
+
const ghUrl = `https://github.com/${GH_REPO}/releases/download/v${version}/${ghName}`;
|
|
136
|
+
// buildUrlList("global") = [直连, ...镜像];R2 排在最前面。
|
|
137
|
+
const urls = [`${R2_RELEASES_BASE}/${file}`, ...buildUrlList(ghUrl, "global")];
|
|
138
|
+
return { file, urls, url: urls[0] };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 第一个 HEAD 得到 200/206 的候选;都不行返回 null。
|
|
142
|
+
async function firstReachable(urls) {
|
|
143
|
+
for (const u of urls) {
|
|
144
|
+
if (await headOk(u)) return u;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
96
147
|
}
|
|
97
148
|
|
|
98
149
|
// ── 下载(带进度,跟随重定向)──────────────────────────────────────────────────
|
|
@@ -179,9 +230,10 @@ async function check() {
|
|
|
179
230
|
if (latest && cmpVer(latest, current) > 0) {
|
|
180
231
|
// 「先出包再更新版本」客户端保险:版本号涨了不代表包传完了。先 HEAD 确认本平台安装包
|
|
181
232
|
// 真能下(200),否则当成「还没就绪」继续显已是最新,避免用户点下载 404。
|
|
182
|
-
const {
|
|
183
|
-
const ready = await
|
|
233
|
+
const { urls } = assetFor(latest);
|
|
234
|
+
const ready = await firstReachable(urls);
|
|
184
235
|
if (ready) {
|
|
236
|
+
log.info(`[app-updater] ${latest} 安装包就绪:${ready}`);
|
|
185
237
|
broadcast({ status: "available", version: latest, current, progress: null, filePath: null, autoUpdate: getAutoUpdate(), auto: false });
|
|
186
238
|
if (getAutoUpdate()) {
|
|
187
239
|
log.info(`[app-updater] auto-update on → downloading ${latest} and installing without asking`);
|
|
@@ -189,7 +241,7 @@ async function check() {
|
|
|
189
241
|
await downloadUpdate();
|
|
190
242
|
if (_state.status === "ready") installNow();
|
|
191
243
|
}
|
|
192
|
-
} else { log.info(`[app-updater] ${latest}
|
|
244
|
+
} else { log.info(`[app-updater] ${latest} 版本号已更新但所有来源都拿不到安装包(HEAD 非 200):${urls.join(" , ")} — 暂不提示更新`); broadcast({ status: "up-to-date", version: current, current }); }
|
|
193
245
|
} else {
|
|
194
246
|
broadcast({ status: "up-to-date", version: latest || current, current });
|
|
195
247
|
}
|
|
@@ -208,16 +260,26 @@ async function downloadUpdate() {
|
|
|
208
260
|
if (!version) { broadcast({ status: "error", error: "no version" }); return _state; }
|
|
209
261
|
_downloading = true;
|
|
210
262
|
try {
|
|
211
|
-
const {
|
|
263
|
+
const { urls, file } = assetFor(version);
|
|
212
264
|
// 下到 ~/Downloads(用户能直接找到安装包);目录不存在则建。
|
|
213
265
|
const dir = app.getPath("downloads");
|
|
214
266
|
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
215
267
|
const dest = path.join(dir, file);
|
|
216
|
-
log.info(`[app-updater] downloading ${url} → ${dest}`);
|
|
217
268
|
broadcast({ status: "downloading", progress: { percent: 0, transferred: 0, total: 0 }, filePath: null });
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
269
|
+
// 逐个候选试(R2 → GitHub → 镜像):一个源断了就换下一个,全断才算失败。
|
|
270
|
+
let lastErr = null;
|
|
271
|
+
for (const url of urls) {
|
|
272
|
+
try {
|
|
273
|
+
log.info(`[app-updater] downloading ${url} → ${dest}`);
|
|
274
|
+
await download(url, dest, (p) => broadcast({ status: "downloading", progress: p }));
|
|
275
|
+
broadcast({ status: "ready", filePath: dest, progress: { percent: 100 } });
|
|
276
|
+
return _state;
|
|
277
|
+
} catch (e) {
|
|
278
|
+
lastErr = e;
|
|
279
|
+
log.warn(`[app-updater] 源失败(${url}):${e.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
throw lastErr || new Error("no source available");
|
|
221
283
|
} catch (e) {
|
|
222
284
|
log.warn("[app-updater] download failed:", e.message);
|
|
223
285
|
broadcast({ status: "error", error: e.message });
|
|
@@ -316,6 +316,14 @@ contextBridge.exposeInMainWorld("cicy", {
|
|
|
316
316
|
cloud: {
|
|
317
317
|
fetch: (url, opts) => logInvoke("cloud:fetch", { url, ...(opts || {}) }),
|
|
318
318
|
},
|
|
319
|
+
// "+ 面板" dropdown config — which panels the menu offers, in what order, under
|
|
320
|
+
// what name. get() returns every built-in with its current title/enabled state
|
|
321
|
+
// (so the settings UI can render the full list, not just the enabled ones);
|
|
322
|
+
// set() persists and returns the merged result.
|
|
323
|
+
panelMenu: {
|
|
324
|
+
get: () => logInvoke("panelMenu:get"),
|
|
325
|
+
set: (items) => logInvoke("panelMenu:set", items),
|
|
326
|
+
},
|
|
319
327
|
// CiCy Hub — email sign-in, then every cicy-code instance of that account.
|
|
320
328
|
hub: {
|
|
321
329
|
status: () => logInvoke("hub:status"),
|