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.
@@ -16,7 +16,10 @@ function createWebBridge() {
16
16
 
17
17
  const payload = await response.json();
18
18
  if (!response.ok || !payload.ok) {
19
- throw new Error(payload.error || `请求失败:${response.status}`);
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
- $("#saveEndpointButton").textContent = "新增连接";
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
- $("#saveEndpointButton").textContent = "保存修改";
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("#cloudServerUrl", cloud.serverUrl);
195
- syncFieldValue("#cloudUsername", cloud.username);
258
+ syncFieldValue("#authUsername", cloud.username);
196
259
 
197
- const authText = !cloud.serverUrl
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
- ? `已登录:${cloud.remoteUser || cloud.username}`
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
- $("#cloudAuthStatus").textContent = authText;
223
- $("#cloudRemoteStatus").textContent = remoteText;
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 getCloudFormPayload() {
509
+ function getAuthFormPayload() {
384
510
  return {
385
- serverUrl: $("#cloudServerUrl").value.trim(),
386
- username: $("#cloudUsername").value.trim(),
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
- setStatus("正在注册同步账号 ...", "info");
394
- const payload = getCloudFormPayload();
518
+ setAuthStatus("正在注册同步账号 ...", "info");
519
+ const payload = getAuthFormPayload();
395
520
  const result = await bridge.registerCloudAccount(payload);
396
- $("#cloudPassword").value = "";
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
- setStatus(`注册失败:${error.message}`, "error");
528
+ setAuthStatus(`注册失败:${error.message}`, "error");
401
529
  }
402
530
  }
403
531
 
404
532
  async function handleCloudLogin() {
405
533
  try {
406
- setStatus("正在登录同步账号 ...", "info");
407
- const payload = getCloudFormPayload();
534
+ setAuthStatus("正在登录同步账号 ...", "info");
535
+ const payload = getAuthFormPayload();
408
536
  const result = await bridge.loginCloudAccount(payload);
409
- $("#cloudPassword").value = "";
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
- setStatus(`登录失败:${error.message}`, "error");
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
- await refreshDashboard(false);
422
- setStatus(result.message || "已退出同步账号。", "success");
552
+ $("#authPassword").value = "";
553
+ state.cloud = result;
554
+ renderCloudStatus();
555
+ applyAccessState(false);
556
+ setAuthStatus(result.message || "已退出同步账号。", "success");
423
557
  } catch (error) {
424
- setStatus(`退出失败:${error.message}`, "error");
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("已清空表单。", "info");
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 refreshDashboard(false);
563
- setStatus("应用已就绪,你可以直接新增 URL / Key / 备注 并切换。", "success");
745
+ await bootstrapConsoleAccess();
564
746
  });