codex-endpoint-switcher 1.1.0 → 1.2.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/package.json +1 -1
- package/src/main/cloud-sync-client.js +12 -7
- package/src/renderer/index.html +208 -156
- package/src/renderer/renderer.js +223 -41
- package/src/renderer/styles.css +571 -120
- package/src/web/server.js +26 -1
package/src/renderer/renderer.js
CHANGED
|
@@ -16,7 +16,10 @@ function createWebBridge() {
|
|
|
16
16
|
|
|
17
17
|
const payload = await response.json();
|
|
18
18
|
if (!response.ok || !payload.ok) {
|
|
19
|
-
|
|
19
|
+
const error = new Error(payload.error || `请求失败:${response.status}`);
|
|
20
|
+
error.statusCode = response.status;
|
|
21
|
+
error.payload = payload;
|
|
22
|
+
throw error;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
return payload.data;
|
|
@@ -124,10 +127,24 @@ function formatDateTime(value) {
|
|
|
124
127
|
|
|
125
128
|
function setStatus(message, type = "info") {
|
|
126
129
|
const statusBox = $("#statusBox");
|
|
130
|
+
if (!statusBox) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
127
134
|
statusBox.textContent = message;
|
|
128
135
|
statusBox.className = `status-box ${type}`;
|
|
129
136
|
}
|
|
130
137
|
|
|
138
|
+
function setAuthStatus(message, type = "info") {
|
|
139
|
+
const statusBox = $("#authStatusBox");
|
|
140
|
+
if (!statusBox) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
statusBox.textContent = message;
|
|
145
|
+
statusBox.className = `status-box ${type} auth-status`;
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
function syncFieldValue(selector, value) {
|
|
132
149
|
const input = $(selector);
|
|
133
150
|
if (!input) {
|
|
@@ -141,12 +158,59 @@ function syncFieldValue(selector, value) {
|
|
|
141
158
|
input.value = value || "";
|
|
142
159
|
}
|
|
143
160
|
|
|
161
|
+
function applyAccessState(loggedIn) {
|
|
162
|
+
const authGate = $("#authGate");
|
|
163
|
+
const appShell = $("#appShell");
|
|
164
|
+
|
|
165
|
+
if (authGate) {
|
|
166
|
+
authGate.hidden = loggedIn;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (appShell) {
|
|
170
|
+
appShell.hidden = !loggedIn;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!loggedIn && $("#endpointModal") && !$("#endpointModal").hidden) {
|
|
174
|
+
closeEndpointModal();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isUnauthorizedError(error) {
|
|
179
|
+
return Number(error?.statusCode || 0) === 401;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function setEndpointModalContent(mode, note = "") {
|
|
183
|
+
const isEdit = mode === "edit";
|
|
184
|
+
$("#endpointModalTitle").textContent = isEdit ? "编辑连接" : "新增连接";
|
|
185
|
+
$("#endpointModalText").textContent = isEdit
|
|
186
|
+
? `正在编辑“${note}”,保存后会直接更新这条连接。`
|
|
187
|
+
: "填写备注、URL、Key,保存后就能直接加入切换列表。";
|
|
188
|
+
$("#saveEndpointButton").textContent = isEdit ? "保存修改" : "新增连接";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function openEndpointModal() {
|
|
192
|
+
const modal = $("#endpointModal");
|
|
193
|
+
modal.hidden = false;
|
|
194
|
+
document.body.classList.add("modal-open");
|
|
195
|
+
|
|
196
|
+
window.requestAnimationFrame(() => {
|
|
197
|
+
const input = $("#endpointNote");
|
|
198
|
+
input?.focus();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function closeEndpointModal() {
|
|
203
|
+
const modal = $("#endpointModal");
|
|
204
|
+
modal.hidden = true;
|
|
205
|
+
document.body.classList.remove("modal-open");
|
|
206
|
+
}
|
|
207
|
+
|
|
144
208
|
function resetForm() {
|
|
145
209
|
$("#endpointId").value = "";
|
|
146
210
|
$("#endpointNote").value = "";
|
|
147
211
|
$("#endpointUrl").value = "";
|
|
148
212
|
$("#endpointKey").value = "";
|
|
149
|
-
|
|
213
|
+
setEndpointModalContent("create");
|
|
150
214
|
}
|
|
151
215
|
|
|
152
216
|
function fillForm(endpoint) {
|
|
@@ -154,7 +218,7 @@ function fillForm(endpoint) {
|
|
|
154
218
|
$("#endpointNote").value = endpoint.note;
|
|
155
219
|
$("#endpointUrl").value = endpoint.url;
|
|
156
220
|
$("#endpointKey").value = endpoint.key;
|
|
157
|
-
|
|
221
|
+
setEndpointModalContent("edit", endpoint.note);
|
|
158
222
|
setStatus(`正在编辑:${endpoint.note}`, "info");
|
|
159
223
|
}
|
|
160
224
|
|
|
@@ -191,23 +255,26 @@ function renderCloudStatus() {
|
|
|
191
255
|
return;
|
|
192
256
|
}
|
|
193
257
|
|
|
194
|
-
syncFieldValue("#
|
|
195
|
-
syncFieldValue("#cloudUsername", cloud.username);
|
|
258
|
+
syncFieldValue("#authUsername", cloud.username);
|
|
196
259
|
|
|
197
|
-
const
|
|
198
|
-
|
|
260
|
+
const accountName = cloud.remoteUser || cloud.username || "-";
|
|
261
|
+
const serverText = !cloud.serverUrl
|
|
262
|
+
? "未配置"
|
|
263
|
+
: cloud.loggedIn
|
|
264
|
+
? "已自动连接"
|
|
265
|
+
: cloud.lastError
|
|
266
|
+
? "连接异常"
|
|
267
|
+
: "等待登录";
|
|
268
|
+
const sessionText = !cloud.serverUrl
|
|
269
|
+
? "未配置服务器"
|
|
199
270
|
: cloud.loggedIn
|
|
200
|
-
?
|
|
271
|
+
? cloud.tokenExpiresAt
|
|
272
|
+
? `已登录 · 到期 ${formatDateTime(cloud.tokenExpiresAt)}`
|
|
273
|
+
: "已登录"
|
|
201
274
|
: cloud.hasToken
|
|
202
|
-
? "
|
|
275
|
+
? "登录态失效,请重新登录"
|
|
203
276
|
: "未登录";
|
|
204
277
|
|
|
205
|
-
const remoteText = cloud.serverUrl
|
|
206
|
-
? cloud.lastError
|
|
207
|
-
? `${cloud.serverUrl} · ${cloud.lastError}`
|
|
208
|
-
: cloud.serverUrl
|
|
209
|
-
: "未设置";
|
|
210
|
-
|
|
211
278
|
const syncStatusParts = [];
|
|
212
279
|
if (cloud.lastPushAt) {
|
|
213
280
|
syncStatusParts.push(`最近推送:${formatDateTime(cloud.lastPushAt)}`);
|
|
@@ -219,9 +286,15 @@ function renderCloudStatus() {
|
|
|
219
286
|
syncStatusParts.push("还没有推送或拉取记录");
|
|
220
287
|
}
|
|
221
288
|
|
|
222
|
-
$("#
|
|
223
|
-
$("#
|
|
289
|
+
$("#authAccessTag").textContent = cloud.loggedIn ? "已登录" : "未登录";
|
|
290
|
+
$("#cloudAccessTag").textContent = cloud.loggedIn ? "控制台已解锁" : "未登录";
|
|
291
|
+
$("#cloudAccountName").textContent = accountName;
|
|
292
|
+
$("#cloudServerStatus").textContent = serverText;
|
|
293
|
+
$("#cloudSessionStatus").textContent = cloud.lastError
|
|
294
|
+
? `${sessionText} · ${cloud.lastError}`
|
|
295
|
+
: sessionText;
|
|
224
296
|
$("#cloudLastSyncStatus").textContent = syncStatusParts.join(" / ");
|
|
297
|
+
$("#openSwitchAccountButton").disabled = !cloud.loggedIn;
|
|
225
298
|
$("#cloudLogoutButton").disabled = !cloud.hasToken;
|
|
226
299
|
$("#cloudPushButton").disabled = !cloud.loggedIn;
|
|
227
300
|
$("#cloudPullMergeButton").disabled = !cloud.loggedIn;
|
|
@@ -274,6 +347,7 @@ function createEndpointCard(endpoint) {
|
|
|
274
347
|
editButton.textContent = "编辑";
|
|
275
348
|
editButton.addEventListener("click", () => {
|
|
276
349
|
fillForm(endpoint);
|
|
350
|
+
openEndpointModal();
|
|
277
351
|
});
|
|
278
352
|
|
|
279
353
|
const deleteButton = document.createElement("button");
|
|
@@ -305,7 +379,7 @@ function renderEndpoints() {
|
|
|
305
379
|
if (state.endpoints.length === 0) {
|
|
306
380
|
const empty = document.createElement("div");
|
|
307
381
|
empty.className = "empty-state";
|
|
308
|
-
empty.textContent = "
|
|
382
|
+
empty.textContent = "当前还没有连接,点击右上角“新增连接”先建一条。";
|
|
309
383
|
list.appendChild(empty);
|
|
310
384
|
} else {
|
|
311
385
|
state.endpoints.forEach((endpoint) => {
|
|
@@ -316,6 +390,43 @@ function renderEndpoints() {
|
|
|
316
390
|
$("#endpointCount").textContent = `${state.endpoints.length} 条`;
|
|
317
391
|
}
|
|
318
392
|
|
|
393
|
+
async function handleAccessRevoked(message) {
|
|
394
|
+
try {
|
|
395
|
+
state.cloud = await bridge.getCloudSyncStatus();
|
|
396
|
+
renderCloudStatus();
|
|
397
|
+
} catch {
|
|
398
|
+
// 登录态接口异常时,至少先把控制台切回登录页。
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
applyAccessState(false);
|
|
402
|
+
setAuthStatus(message || "请先登录同步账号后再进入连接控制台。", "error");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function bootstrapConsoleAccess() {
|
|
406
|
+
try {
|
|
407
|
+
const cloud = await bridge.getCloudSyncStatus();
|
|
408
|
+
state.cloud = cloud;
|
|
409
|
+
renderCloudStatus();
|
|
410
|
+
|
|
411
|
+
if (!cloud.loggedIn) {
|
|
412
|
+
applyAccessState(false);
|
|
413
|
+
if (cloud.hasToken && cloud.lastError) {
|
|
414
|
+
setAuthStatus(`登录校验失败:${cloud.lastError}`, "error");
|
|
415
|
+
} else {
|
|
416
|
+
setAuthStatus("请先登录同步账号后再进入连接控制台。", "info");
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
applyAccessState(true);
|
|
422
|
+
await refreshDashboard(false);
|
|
423
|
+
setStatus("已通过账号校验,连接控制台已解锁。", "success");
|
|
424
|
+
} catch (error) {
|
|
425
|
+
applyAccessState(false);
|
|
426
|
+
setAuthStatus(`登录校验失败:${error.message}`, "error");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
319
430
|
async function refreshDashboard(showMessage = true) {
|
|
320
431
|
try {
|
|
321
432
|
const [current, endpoints, paths, cloud] = await Promise.all([
|
|
@@ -338,6 +449,11 @@ async function refreshDashboard(showMessage = true) {
|
|
|
338
449
|
setStatus("已刷新当前连接状态。", "success");
|
|
339
450
|
}
|
|
340
451
|
} catch (error) {
|
|
452
|
+
if (isUnauthorizedError(error)) {
|
|
453
|
+
await handleAccessRevoked(error.message);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
341
457
|
setStatus(`刷新失败:${error.message}`, "error");
|
|
342
458
|
}
|
|
343
459
|
}
|
|
@@ -357,6 +473,11 @@ async function handleSwitchEndpoint(id, note) {
|
|
|
357
473
|
}
|
|
358
474
|
return result;
|
|
359
475
|
} catch (error) {
|
|
476
|
+
if (isUnauthorizedError(error)) {
|
|
477
|
+
await handleAccessRevoked(error.message);
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
360
481
|
setStatus(`切换失败:${error.message}`, "error");
|
|
361
482
|
return null;
|
|
362
483
|
}
|
|
@@ -376,41 +497,51 @@ async function handleEnableProxyMode() {
|
|
|
376
497
|
setStatus("热更新模式已开启。当前会话后续请求可直接热更新到新连接。", "success");
|
|
377
498
|
}
|
|
378
499
|
} catch (error) {
|
|
500
|
+
if (isUnauthorizedError(error)) {
|
|
501
|
+
await handleAccessRevoked(error.message);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
379
505
|
setStatus(`开启热更新模式失败:${error.message}`, "error");
|
|
380
506
|
}
|
|
381
507
|
}
|
|
382
508
|
|
|
383
|
-
function
|
|
509
|
+
function getAuthFormPayload() {
|
|
384
510
|
return {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
password: $("#cloudPassword").value,
|
|
511
|
+
username: $("#authUsername").value.trim(),
|
|
512
|
+
password: $("#authPassword").value,
|
|
388
513
|
};
|
|
389
514
|
}
|
|
390
515
|
|
|
391
516
|
async function handleCloudRegister() {
|
|
392
517
|
try {
|
|
393
|
-
|
|
394
|
-
const payload =
|
|
518
|
+
setAuthStatus("正在注册同步账号 ...", "info");
|
|
519
|
+
const payload = getAuthFormPayload();
|
|
395
520
|
const result = await bridge.registerCloudAccount(payload);
|
|
396
|
-
$("#
|
|
521
|
+
$("#authPassword").value = "";
|
|
522
|
+
state.cloud = result;
|
|
523
|
+
renderCloudStatus();
|
|
524
|
+
applyAccessState(true);
|
|
397
525
|
await refreshDashboard(false);
|
|
398
526
|
setStatus(result.message || "账号注册成功。", "success");
|
|
399
527
|
} catch (error) {
|
|
400
|
-
|
|
528
|
+
setAuthStatus(`注册失败:${error.message}`, "error");
|
|
401
529
|
}
|
|
402
530
|
}
|
|
403
531
|
|
|
404
532
|
async function handleCloudLogin() {
|
|
405
533
|
try {
|
|
406
|
-
|
|
407
|
-
const payload =
|
|
534
|
+
setAuthStatus("正在登录同步账号 ...", "info");
|
|
535
|
+
const payload = getAuthFormPayload();
|
|
408
536
|
const result = await bridge.loginCloudAccount(payload);
|
|
409
|
-
$("#
|
|
537
|
+
$("#authPassword").value = "";
|
|
538
|
+
state.cloud = result;
|
|
539
|
+
renderCloudStatus();
|
|
540
|
+
applyAccessState(true);
|
|
410
541
|
await refreshDashboard(false);
|
|
411
542
|
setStatus(result.message || "账号登录成功。", "success");
|
|
412
543
|
} catch (error) {
|
|
413
|
-
|
|
544
|
+
setAuthStatus(`登录失败:${error.message}`, "error");
|
|
414
545
|
}
|
|
415
546
|
}
|
|
416
547
|
|
|
@@ -418,10 +549,13 @@ async function handleCloudLogout() {
|
|
|
418
549
|
try {
|
|
419
550
|
setStatus("正在退出同步账号 ...", "info");
|
|
420
551
|
const result = await bridge.logoutCloudAccount();
|
|
421
|
-
|
|
422
|
-
|
|
552
|
+
$("#authPassword").value = "";
|
|
553
|
+
state.cloud = result;
|
|
554
|
+
renderCloudStatus();
|
|
555
|
+
applyAccessState(false);
|
|
556
|
+
setAuthStatus(result.message || "已退出同步账号。", "success");
|
|
423
557
|
} catch (error) {
|
|
424
|
-
|
|
558
|
+
setAuthStatus(`退出失败:${error.message}`, "error");
|
|
425
559
|
}
|
|
426
560
|
}
|
|
427
561
|
|
|
@@ -435,6 +569,11 @@ async function handleCloudPush() {
|
|
|
435
569
|
"success",
|
|
436
570
|
);
|
|
437
571
|
} catch (error) {
|
|
572
|
+
if (isUnauthorizedError(error)) {
|
|
573
|
+
await handleAccessRevoked(error.message);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
438
577
|
setStatus(`推送失败:${error.message}`, "error");
|
|
439
578
|
}
|
|
440
579
|
}
|
|
@@ -461,6 +600,11 @@ async function handleCloudPull(mode) {
|
|
|
461
600
|
"success",
|
|
462
601
|
);
|
|
463
602
|
} catch (error) {
|
|
603
|
+
if (isUnauthorizedError(error)) {
|
|
604
|
+
await handleAccessRevoked(error.message);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
464
608
|
setStatus(`拉取失败:${error.message}`, "error");
|
|
465
609
|
}
|
|
466
610
|
}
|
|
@@ -480,15 +624,20 @@ async function handleSubmitEndpoint(event) {
|
|
|
480
624
|
try {
|
|
481
625
|
if (id) {
|
|
482
626
|
await bridge.updateEndpoint({ id, note, url, key });
|
|
483
|
-
setStatus(`已更新连接:${note}`, "success");
|
|
484
627
|
} else {
|
|
485
628
|
await bridge.createEndpoint({ note, url, key });
|
|
486
|
-
setStatus(`已新增连接:${note}`, "success");
|
|
487
629
|
}
|
|
488
630
|
|
|
489
|
-
resetForm();
|
|
490
631
|
await refreshDashboard(false);
|
|
632
|
+
closeEndpointModal();
|
|
633
|
+
resetForm();
|
|
634
|
+
setStatus(id ? `已更新连接:${note}` : `已新增连接:${note}`, "success");
|
|
491
635
|
} catch (error) {
|
|
636
|
+
if (isUnauthorizedError(error)) {
|
|
637
|
+
await handleAccessRevoked(error.message);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
492
641
|
setStatus(`保存失败:${error.message}`, "error");
|
|
493
642
|
}
|
|
494
643
|
}
|
|
@@ -502,11 +651,17 @@ async function handleDeleteEndpoint(id, note) {
|
|
|
502
651
|
try {
|
|
503
652
|
await bridge.deleteEndpoint({ id });
|
|
504
653
|
if ($("#endpointId").value.trim() === id) {
|
|
654
|
+
closeEndpointModal();
|
|
505
655
|
resetForm();
|
|
506
656
|
}
|
|
507
657
|
await refreshDashboard(false);
|
|
508
658
|
setStatus(`已删除连接:${note}`, "success");
|
|
509
659
|
} catch (error) {
|
|
660
|
+
if (isUnauthorizedError(error)) {
|
|
661
|
+
await handleAccessRevoked(error.message);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
510
665
|
setStatus(`删除失败:${error.message}`, "error");
|
|
511
666
|
}
|
|
512
667
|
}
|
|
@@ -520,23 +675,46 @@ async function handleOpenPath(targetPath, label) {
|
|
|
520
675
|
|
|
521
676
|
setStatus(`已打开${label}。`, "success");
|
|
522
677
|
} catch (error) {
|
|
678
|
+
if (isUnauthorizedError(error)) {
|
|
679
|
+
await handleAccessRevoked(error.message);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
523
683
|
setStatus(`打开${label}失败:${error.message}`, "error");
|
|
524
684
|
}
|
|
525
685
|
}
|
|
526
686
|
|
|
527
687
|
function bindEvents() {
|
|
688
|
+
$("#authForm").addEventListener("submit", (event) => {
|
|
689
|
+
event.preventDefault();
|
|
690
|
+
handleCloudLogin();
|
|
691
|
+
});
|
|
692
|
+
$("#authRegisterButton").addEventListener("click", () => {
|
|
693
|
+
handleCloudRegister();
|
|
694
|
+
});
|
|
528
695
|
$("#refreshButton").addEventListener("click", () => {
|
|
529
696
|
refreshDashboard();
|
|
530
697
|
});
|
|
698
|
+
$("#openCreateEndpointButton").addEventListener("click", () => {
|
|
699
|
+
resetForm();
|
|
700
|
+
openEndpointModal();
|
|
701
|
+
});
|
|
702
|
+
$("#closeEndpointModalButton").addEventListener("click", () => {
|
|
703
|
+
closeEndpointModal();
|
|
704
|
+
});
|
|
705
|
+
$("#endpointModal").addEventListener("click", (event) => {
|
|
706
|
+
if (event.target.dataset.modalClose === "true") {
|
|
707
|
+
closeEndpointModal();
|
|
708
|
+
}
|
|
709
|
+
});
|
|
531
710
|
$("#endpointForm").addEventListener("submit", handleSubmitEndpoint);
|
|
532
711
|
$("#clearFormButton").addEventListener("click", () => {
|
|
533
712
|
resetForm();
|
|
534
|
-
setStatus("
|
|
713
|
+
setStatus("已重置表单,可作为新连接保存。", "info");
|
|
535
714
|
});
|
|
536
715
|
$("#enableProxyModeButton").addEventListener("click", handleEnableProxyMode);
|
|
537
|
-
$("#cloudRegisterButton").addEventListener("click", handleCloudRegister);
|
|
538
|
-
$("#cloudLoginButton").addEventListener("click", handleCloudLogin);
|
|
539
716
|
$("#cloudLogoutButton").addEventListener("click", handleCloudLogout);
|
|
717
|
+
$("#openSwitchAccountButton").addEventListener("click", handleCloudLogout);
|
|
540
718
|
$("#cloudPushButton").addEventListener("click", handleCloudPush);
|
|
541
719
|
$("#cloudPullMergeButton").addEventListener("click", () => {
|
|
542
720
|
handleCloudPull("merge");
|
|
@@ -554,11 +732,15 @@ function bindEvents() {
|
|
|
554
732
|
handleOpenPath(state.paths.endpointStorePath, " 连接数据文件");
|
|
555
733
|
}
|
|
556
734
|
});
|
|
735
|
+
window.addEventListener("keydown", (event) => {
|
|
736
|
+
if (event.key === "Escape" && !$("#endpointModal").hidden) {
|
|
737
|
+
closeEndpointModal();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
557
740
|
}
|
|
558
741
|
|
|
559
742
|
window.addEventListener("DOMContentLoaded", async () => {
|
|
560
743
|
bindEvents();
|
|
561
744
|
resetForm();
|
|
562
|
-
await
|
|
563
|
-
setStatus("应用已就绪,你可以直接新增 URL / Key / 备注 并切换。", "success");
|
|
745
|
+
await bootstrapConsoleAccess();
|
|
564
746
|
});
|