cicy-desktop 2.1.134 → 2.1.136
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/backends/auth-loopback.js +61 -9
- package/src/sidecar/wsl-docker.js +36 -10
package/package.json
CHANGED
|
@@ -47,6 +47,45 @@ const LOGIN_TIMEOUT_MS = 120_000;
|
|
|
47
47
|
let _server = null;
|
|
48
48
|
let _state = null;
|
|
49
49
|
let _timeoutHandle = null;
|
|
50
|
+
let _loginWindow = null;
|
|
51
|
+
let _onResult = null; // the caller's callback, for the user-closes-window case
|
|
52
|
+
let _resultFired = false; // guard: onResult is delivered exactly once
|
|
53
|
+
|
|
54
|
+
function closeLoginWindow() {
|
|
55
|
+
const w = _loginWindow;
|
|
56
|
+
_loginWindow = null;
|
|
57
|
+
if (w && !w.isDestroyed()) { try { w.removeAllListeners("closed"); w.close(); } catch {} }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Open the login page in an IN-APP Electron window. This is the fix for "登录弹不出
|
|
61
|
+
//网页": shell.openExternal needs a working default browser, which a fresh Windows
|
|
62
|
+
// profile may not have. An in-app window has no such dependency, and the cloud's
|
|
63
|
+
// 302 to http://127.0.0.1:<port>/cb lands right back in THIS window — the loopback
|
|
64
|
+
// server answers it, firing onResult. Returns false if the window can't be made
|
|
65
|
+
// (then the caller falls back to the system browser).
|
|
66
|
+
function openLoginWindow(url) {
|
|
67
|
+
const { BrowserWindow } = require("electron");
|
|
68
|
+
try {
|
|
69
|
+
closeLoginWindow();
|
|
70
|
+
_loginWindow = new BrowserWindow({
|
|
71
|
+
width: 480, height: 760, title: "登录 CiCy", autoHideMenuBar: true, show: true,
|
|
72
|
+
webPreferences: { nodeIntegration: false, contextIsolation: true, partition: "persist:cicy-login" },
|
|
73
|
+
});
|
|
74
|
+
_loginWindow.loadURL(url);
|
|
75
|
+
// User closed the window before login completed → treat as cancel so the
|
|
76
|
+
// renderer's "登录中…" state is released instead of hanging until timeout.
|
|
77
|
+
_loginWindow.on("closed", () => {
|
|
78
|
+
_loginWindow = null;
|
|
79
|
+
if (_resultFired) return; // success already delivered; it shuts us down
|
|
80
|
+
try { _onResult && _onResult({ error: "login window closed" }); } catch {}
|
|
81
|
+
shutdown("login window closed by user");
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
} catch (e) {
|
|
85
|
+
log.warn(`[auth-loopback] in-app login window failed: ${e.message}`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
50
89
|
|
|
51
90
|
function shutdown(reason) {
|
|
52
91
|
if (_timeoutHandle) { clearTimeout(_timeoutHandle); _timeoutHandle = null; }
|
|
@@ -54,6 +93,7 @@ function shutdown(reason) {
|
|
|
54
93
|
try { _server.close(); } catch {}
|
|
55
94
|
_server = null;
|
|
56
95
|
}
|
|
96
|
+
closeLoginWindow();
|
|
57
97
|
_state = null;
|
|
58
98
|
if (reason) log.info(`[auth-loopback] shutdown: ${reason}`);
|
|
59
99
|
}
|
|
@@ -64,6 +104,14 @@ async function startLogin({ onResult } = {}) {
|
|
|
64
104
|
|
|
65
105
|
_state = crypto.randomBytes(16).toString("hex");
|
|
66
106
|
const expectedState = _state;
|
|
107
|
+
_resultFired = false;
|
|
108
|
+
// Deliver the result to the caller exactly once — the server callback, the
|
|
109
|
+
// user-closes-window handler, and the timeout all race to be first.
|
|
110
|
+
_onResult = (payload) => {
|
|
111
|
+
if (_resultFired) return;
|
|
112
|
+
_resultFired = true;
|
|
113
|
+
try { onResult && onResult(payload); } catch {}
|
|
114
|
+
};
|
|
67
115
|
|
|
68
116
|
_server = http.createServer((req, res) => {
|
|
69
117
|
let url;
|
|
@@ -98,7 +146,7 @@ async function startLogin({ onResult } = {}) {
|
|
|
98
146
|
<h1 style="color:#c00">登录失败</h1>
|
|
99
147
|
<p>state 校验未通过,请回到 CiCy Desktop 重新点击 Login。</p>
|
|
100
148
|
</body>`);
|
|
101
|
-
|
|
149
|
+
_onResult({ error: "state mismatch" });
|
|
102
150
|
setTimeout(() => shutdown("state mismatch"), 500);
|
|
103
151
|
return;
|
|
104
152
|
}
|
|
@@ -110,14 +158,14 @@ async function startLogin({ onResult } = {}) {
|
|
|
110
158
|
<h1 style="color:#c00">登录失败</h1>
|
|
111
159
|
<p>未收到 token,请回到 CiCy Desktop 重试。</p>
|
|
112
160
|
</body>`);
|
|
113
|
-
|
|
161
|
+
_onResult({ error: "no token" });
|
|
114
162
|
setTimeout(() => shutdown("no token"), 500);
|
|
115
163
|
return;
|
|
116
164
|
}
|
|
117
165
|
|
|
118
166
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
119
167
|
res.end(SUCCESS_HTML);
|
|
120
|
-
|
|
168
|
+
_onResult({ token, state, reused, accessToken, userId });
|
|
121
169
|
setTimeout(() => shutdown("login complete"), 500);
|
|
122
170
|
});
|
|
123
171
|
|
|
@@ -133,14 +181,18 @@ async function startLogin({ onResult } = {}) {
|
|
|
133
181
|
});
|
|
134
182
|
const url = `${LOGIN_BASE}?${params.toString()}`;
|
|
135
183
|
log.info(`[auth-loopback] listening :${port}, opening ${url}`);
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
184
|
+
// Open the login page IN-APP (an Electron window) — works even when the
|
|
185
|
+
// machine has no/locked default browser, which is the "登录弹不出网页" bug.
|
|
186
|
+
// The cloud's 302 to http://127.0.0.1:<port>/cb lands back in this same
|
|
187
|
+
// window and the loopback server answers it. Only if the window can't be
|
|
188
|
+
// created do we fall back to the system browser. We still return the url so
|
|
189
|
+
// the renderer can offer a manual "open / copy link" last resort.
|
|
190
|
+
if (!openLoginWindow(url)) {
|
|
191
|
+
require("./open-external").openExternalRobust(url).catch((e) => log.warn(`[auth-loopback] open failed: ${e.message}`));
|
|
192
|
+
}
|
|
141
193
|
|
|
142
194
|
_timeoutHandle = setTimeout(() => {
|
|
143
|
-
|
|
195
|
+
_onResult({ error: "timeout" });
|
|
144
196
|
shutdown("timeout");
|
|
145
197
|
}, LOGIN_TIMEOUT_MS);
|
|
146
198
|
|
|
@@ -121,6 +121,18 @@ function importTarball(dest, installDir) {
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// `wsl --terminate <distro>`: stop the distro so the NEXT `wsl -d` cold-boots it
|
|
125
|
+
// clean. This is the fix for the「引擎没起来」-on-fresh-install failure: a distro
|
|
126
|
+
// straight out of `wsl --import` is frequently half-initialized / unresponsive,
|
|
127
|
+
// so startEngine's first `wsl -d` command times out, dockerd never launches, and
|
|
128
|
+
// its log is never even created (exactly what we saw on a new Windows user). A
|
|
129
|
+
// terminate + cold boot makes that first real command hit a clean distro.
|
|
130
|
+
function wslTerminate() {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
execFile("wsl", ["--terminate", DISTRO], { timeout: 30000, windowsHide: true }, () => resolve());
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
async function installDistro({ emit } = {}) {
|
|
125
137
|
// 1) Download the PRE-BAKED rootfs (Ubuntu+Docker+image baked in, ~444MB) with
|
|
126
138
|
// a real progress bar. curl is ~10× faster than node's downloader on OSS.
|
|
@@ -147,7 +159,13 @@ async function installDistro({ emit } = {}) {
|
|
|
147
159
|
await ensureWslKernel({ emit });
|
|
148
160
|
await importTarball(dest, installDir);
|
|
149
161
|
}
|
|
150
|
-
// 3)
|
|
162
|
+
// 3) Force a clean cold boot. A freshly-imported distro is often wedged, so the
|
|
163
|
+
// next `wsl -d` (startEngine) would time out → dockerd never starts. Terminate
|
|
164
|
+
// now so that first real command boots a clean distro instead.
|
|
165
|
+
emit && emit({ phase: "container", status: "running", message: "重置运行环境(冷启动)…" });
|
|
166
|
+
try { await wslTerminate(); } catch {}
|
|
167
|
+
|
|
168
|
+
// 4) Free the ~444MB package now that the distro has everything.
|
|
151
169
|
try { fs.unlinkSync(dest); } catch {}
|
|
152
170
|
}
|
|
153
171
|
|
|
@@ -195,6 +213,7 @@ async function startEngine() {
|
|
|
195
213
|
// attempts we hard-kill any half-dead daemon so the next launch is clean.
|
|
196
214
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
197
215
|
const at0 = Date.now();
|
|
216
|
+
let launchErr = null;
|
|
198
217
|
log.info(`[startEngine] attempt ${attempt}/3 — (re)launch dockerd + wait for socket`);
|
|
199
218
|
try {
|
|
200
219
|
await wslRun(
|
|
@@ -213,17 +232,24 @@ async function startEngine() {
|
|
|
213
232
|
// cold again (a restart loop → 「引擎没起来」even though dockerd was fine).
|
|
214
233
|
"for i in $(seq 1 120); do [ -S /var/run/docker.sock ] && docker version >/dev/null 2>&1 && break; sleep 1; done",
|
|
215
234
|
{ timeout: 150000 });
|
|
216
|
-
} catch (e) { log.warn(`[startEngine] attempt ${attempt} launch/wait errored: ${e.message}`); }
|
|
235
|
+
} catch (e) { launchErr = e; log.warn(`[startEngine] attempt ${attempt} launch/wait errored: ${e.message}`); }
|
|
217
236
|
if (await dockerEngineUp()) { log.info(`[startEngine] ✓ dockerd up on attempt ${attempt} (${((Date.now() - at0) / 1000).toFixed(1)}s)`); return true; }
|
|
218
237
|
log.warn(`[startEngine] attempt ${attempt} dockerd still not up after ${((Date.now() - at0) / 1000).toFixed(1)}s`);
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
// Reset between attempts. If the launch itself ERRORED (timeout = WSL wedged /
|
|
239
|
+
// unresponsive — the post-import failure where the command can't even get into
|
|
240
|
+
// the distro), `wsl --terminate` so the next attempt cold-boots a CLEAN distro;
|
|
241
|
+
// clearing pid files inside a wedged WSL does nothing. Otherwise dockerd just
|
|
242
|
+
// died/is slow — clear stale runtime files only when it's actually gone.
|
|
243
|
+
if (launchErr) {
|
|
244
|
+
log.info(`[startEngine] launch errored → wsl --terminate for a clean cold boot`);
|
|
245
|
+
try { await wslTerminate(); } catch {}
|
|
246
|
+
} else {
|
|
247
|
+
try {
|
|
248
|
+
await wslRun(
|
|
249
|
+
"if ! pgrep dockerd >/dev/null 2>&1; then rm -f /var/run/docker.pid /run/docker.pid /var/run/docker.sock /run/docker.sock; fi; sleep 1",
|
|
250
|
+
{ timeout: 15000 });
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
227
253
|
}
|
|
228
254
|
log.error("[startEngine] ✗ dockerd NOT up after 3 attempts");
|
|
229
255
|
return false;
|