codex-endpoint-switcher 1.1.0 → 1.3.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;
@@ -106,6 +109,15 @@ function $(selector) {
106
109
  return document.querySelector(selector);
107
110
  }
108
111
 
112
+ function setText(selector, value) {
113
+ const element = $(selector);
114
+ if (!element) {
115
+ return;
116
+ }
117
+
118
+ element.textContent = value;
119
+ }
120
+
109
121
  function formatDateTime(value) {
110
122
  if (!value) {
111
123
  return "-";
@@ -123,9 +135,18 @@ function formatDateTime(value) {
123
135
  }
124
136
 
125
137
  function setStatus(message, type = "info") {
126
- const statusBox = $("#statusBox");
127
- statusBox.textContent = message;
128
- statusBox.className = `status-box ${type}`;
138
+ void message;
139
+ void type;
140
+ }
141
+
142
+ function setAuthStatus(message, type = "info") {
143
+ const authStatusElement = $("#authStatusBox");
144
+ if (!authStatusElement) {
145
+ return;
146
+ }
147
+
148
+ authStatusElement.textContent = message;
149
+ authStatusElement.className = `status-box ${type} auth-status`;
129
150
  }
130
151
 
131
152
  function syncFieldValue(selector, value) {
@@ -141,12 +162,59 @@ function syncFieldValue(selector, value) {
141
162
  input.value = value || "";
142
163
  }
143
164
 
165
+ function applyAccessState(loggedIn) {
166
+ const authGate = $("#authGate");
167
+ const appShell = $("#appShell");
168
+
169
+ if (authGate) {
170
+ authGate.hidden = loggedIn;
171
+ }
172
+
173
+ if (appShell) {
174
+ appShell.hidden = !loggedIn;
175
+ }
176
+
177
+ if (!loggedIn && $("#endpointModal") && !$("#endpointModal").hidden) {
178
+ closeEndpointModal();
179
+ }
180
+ }
181
+
182
+ function isUnauthorizedError(error) {
183
+ return Number(error?.statusCode || 0) === 401;
184
+ }
185
+
186
+ function setEndpointModalContent(mode, note = "") {
187
+ const isEdit = mode === "edit";
188
+ $("#endpointModalTitle").textContent = isEdit ? "编辑连接" : "新增连接";
189
+ $("#endpointModalText").textContent = isEdit
190
+ ? `正在编辑“${note}”,保存后会直接更新这条连接。`
191
+ : "填写备注、URL、Key,保存后就能直接加入切换列表。";
192
+ $("#saveEndpointButton").textContent = isEdit ? "保存修改" : "新增连接";
193
+ }
194
+
195
+ function openEndpointModal() {
196
+ const modal = $("#endpointModal");
197
+ modal.hidden = false;
198
+ document.body.classList.add("modal-open");
199
+
200
+ window.requestAnimationFrame(() => {
201
+ const input = $("#endpointNote");
202
+ input?.focus();
203
+ });
204
+ }
205
+
206
+ function closeEndpointModal() {
207
+ const modal = $("#endpointModal");
208
+ modal.hidden = true;
209
+ document.body.classList.remove("modal-open");
210
+ }
211
+
144
212
  function resetForm() {
145
213
  $("#endpointId").value = "";
146
214
  $("#endpointNote").value = "";
147
215
  $("#endpointUrl").value = "";
148
216
  $("#endpointKey").value = "";
149
- $("#saveEndpointButton").textContent = "新增连接";
217
+ setEndpointModalContent("create");
150
218
  }
151
219
 
152
220
  function fillForm(endpoint) {
@@ -154,7 +222,7 @@ function fillForm(endpoint) {
154
222
  $("#endpointNote").value = endpoint.note;
155
223
  $("#endpointUrl").value = endpoint.url;
156
224
  $("#endpointKey").value = endpoint.key;
157
- $("#saveEndpointButton").textContent = "保存修改";
225
+ setEndpointModalContent("edit", endpoint.note);
158
226
  setStatus(`正在编辑:${endpoint.note}`, "info");
159
227
  }
160
228
 
@@ -164,25 +232,11 @@ function renderCurrent() {
164
232
  return;
165
233
  }
166
234
 
167
- $("#activeEndpointTag").textContent = current.currentNote || "未识别";
168
- $("#currentNote").textContent = current.currentNote || "未识别";
169
- $("#currentProvider").textContent = current.provider || "-";
170
- $("#currentBaseUrl").textContent = current.currentUrl || "-";
171
- $("#currentKeyMasked").textContent = current.currentKeyMasked || "-";
172
- $("#currentModel").textContent = current.model || "-";
173
- $("#proxyModeStatus").textContent = current.proxyModeEnabled
174
- ? `已开启,固定代理:${current.proxyBaseUrl}`
175
- : "未开启";
176
- $("#proxyModeHint").textContent = current.proxyModeEnabled
177
- ? "当前 Codex 只要已经接入本地代理,后续同一会话内切换连接会在下一次请求时热更新。"
178
- : "当前还是直连模式。开启后需要把已打开的旧 Codex 会话重开一次;之后同一会话即可热更新。";
179
- $("#enableProxyModeButton").disabled = current.proxyModeEnabled;
180
- $("#enableProxyModeButton").textContent = current.proxyModeEnabled
181
- ? "热更新模式已开启"
182
- : "开启热更新模式";
183
- $("#heroBadge").textContent = current.currentNote
184
- ? `${current.proxyModeEnabled ? "热更新中" : "当前连接"}:${current.currentNote}`
185
- : "当前未匹配到已保存连接";
235
+ setText("#activeEndpointTag", current.currentNote || "未识别");
236
+ setText("#currentNote", current.currentNote || "未识别");
237
+ setText("#currentProvider", current.provider || "-");
238
+ setText("#currentKeyMasked", current.currentKeyMasked || "-");
239
+ setText("#currentModel", current.model || "-");
186
240
  }
187
241
 
188
242
  function renderCloudStatus() {
@@ -191,22 +245,15 @@ function renderCloudStatus() {
191
245
  return;
192
246
  }
193
247
 
194
- syncFieldValue("#cloudServerUrl", cloud.serverUrl);
195
- syncFieldValue("#cloudUsername", cloud.username);
196
-
197
- const authText = !cloud.serverUrl
198
- ? "未配置同步服务器"
199
- : cloud.loggedIn
200
- ? `已登录:${cloud.remoteUser || cloud.username}`
201
- : cloud.hasToken
202
- ? "本地已保存登录态,但远端校验未通过"
203
- : "未登录";
248
+ syncFieldValue("#authUsername", cloud.username);
204
249
 
205
- const remoteText = cloud.serverUrl
206
- ? cloud.lastError
207
- ? `${cloud.serverUrl} · ${cloud.lastError}`
208
- : cloud.serverUrl
209
- : "未设置";
250
+ const accountName = cloud.remoteUser || cloud.username || "-";
251
+ const serverText = cloud.loggedIn ? "已连接" : cloud.lastError ? "连接异常" : "等待登录";
252
+ const sessionText = cloud.loggedIn
253
+ ? "已登录"
254
+ : cloud.hasToken
255
+ ? "登录态失效,请重新登录"
256
+ : "未登录";
210
257
 
211
258
  const syncStatusParts = [];
212
259
  if (cloud.lastPushAt) {
@@ -219,9 +266,15 @@ function renderCloudStatus() {
219
266
  syncStatusParts.push("还没有推送或拉取记录");
220
267
  }
221
268
 
222
- $("#cloudAuthStatus").textContent = authText;
223
- $("#cloudRemoteStatus").textContent = remoteText;
269
+ $("#authAccessTag").textContent = cloud.loggedIn ? "已登录" : "未登录";
270
+ $("#cloudAccessTag").textContent = cloud.loggedIn ? "控制台已解锁" : "未登录";
271
+ $("#cloudAccountName").textContent = accountName;
272
+ $("#cloudServerStatus").textContent = serverText;
273
+ $("#cloudSessionStatus").textContent = cloud.lastError
274
+ ? `${sessionText} · ${cloud.lastError}`
275
+ : sessionText;
224
276
  $("#cloudLastSyncStatus").textContent = syncStatusParts.join(" / ");
277
+ $("#openSwitchAccountButton").disabled = !cloud.loggedIn;
225
278
  $("#cloudLogoutButton").disabled = !cloud.hasToken;
226
279
  $("#cloudPushButton").disabled = !cloud.loggedIn;
227
280
  $("#cloudPullMergeButton").disabled = !cloud.loggedIn;
@@ -274,6 +327,7 @@ function createEndpointCard(endpoint) {
274
327
  editButton.textContent = "编辑";
275
328
  editButton.addEventListener("click", () => {
276
329
  fillForm(endpoint);
330
+ openEndpointModal();
277
331
  });
278
332
 
279
333
  const deleteButton = document.createElement("button");
@@ -305,7 +359,7 @@ function renderEndpoints() {
305
359
  if (state.endpoints.length === 0) {
306
360
  const empty = document.createElement("div");
307
361
  empty.className = "empty-state";
308
- empty.textContent = "当前还没有连接,先在右侧新增一条。";
362
+ empty.textContent = "当前还没有连接,点击右上角“新增连接”先建一条。";
309
363
  list.appendChild(empty);
310
364
  } else {
311
365
  state.endpoints.forEach((endpoint) => {
@@ -316,6 +370,43 @@ function renderEndpoints() {
316
370
  $("#endpointCount").textContent = `${state.endpoints.length} 条`;
317
371
  }
318
372
 
373
+ async function handleAccessRevoked(message) {
374
+ try {
375
+ state.cloud = await bridge.getCloudSyncStatus();
376
+ renderCloudStatus();
377
+ } catch {
378
+ // 登录态接口异常时,至少先把控制台切回登录页。
379
+ }
380
+
381
+ applyAccessState(false);
382
+ setAuthStatus(message || "请先登录同步账号后再进入连接控制台。", "error");
383
+ }
384
+
385
+ async function bootstrapConsoleAccess() {
386
+ try {
387
+ const cloud = await bridge.getCloudSyncStatus();
388
+ state.cloud = cloud;
389
+ renderCloudStatus();
390
+
391
+ if (!cloud.loggedIn) {
392
+ applyAccessState(false);
393
+ if (cloud.hasToken && cloud.lastError) {
394
+ setAuthStatus(`登录校验失败:${cloud.lastError}`, "error");
395
+ } else {
396
+ setAuthStatus("请先登录同步账号后再进入连接控制台。", "info");
397
+ }
398
+ return;
399
+ }
400
+
401
+ applyAccessState(true);
402
+ await refreshDashboard(false);
403
+ setStatus("已通过账号校验,连接控制台已解锁。", "success");
404
+ } catch (error) {
405
+ applyAccessState(false);
406
+ setAuthStatus(`登录校验失败:${error.message}`, "error");
407
+ }
408
+ }
409
+
319
410
  async function refreshDashboard(showMessage = true) {
320
411
  try {
321
412
  const [current, endpoints, paths, cloud] = await Promise.all([
@@ -338,6 +429,11 @@ async function refreshDashboard(showMessage = true) {
338
429
  setStatus("已刷新当前连接状态。", "success");
339
430
  }
340
431
  } catch (error) {
432
+ if (isUnauthorizedError(error)) {
433
+ await handleAccessRevoked(error.message);
434
+ return;
435
+ }
436
+
341
437
  setStatus(`刷新失败:${error.message}`, "error");
342
438
  }
343
439
  }
@@ -357,60 +453,52 @@ async function handleSwitchEndpoint(id, note) {
357
453
  }
358
454
  return result;
359
455
  } catch (error) {
456
+ if (isUnauthorizedError(error)) {
457
+ await handleAccessRevoked(error.message);
458
+ return null;
459
+ }
460
+
360
461
  setStatus(`切换失败:${error.message}`, "error");
361
462
  return null;
362
463
  }
363
464
  }
364
465
 
365
- async function handleEnableProxyMode() {
366
- try {
367
- setStatus("正在切到热更新代理模式 ...", "info");
368
- const result = await bridge.enableProxyMode();
369
- await refreshDashboard(false);
370
- if (result.oneTimeRestartRequired) {
371
- setStatus(
372
- "热更新模式已开启。请把当前已经打开着的旧 Codex 会话重开一次;之后同一会话内即可热更新。",
373
- "success",
374
- );
375
- } else {
376
- setStatus("热更新模式已开启。当前会话后续请求可直接热更新到新连接。", "success");
377
- }
378
- } catch (error) {
379
- setStatus(`开启热更新模式失败:${error.message}`, "error");
380
- }
381
- }
382
-
383
- function getCloudFormPayload() {
466
+ function getAuthFormPayload() {
384
467
  return {
385
- serverUrl: $("#cloudServerUrl").value.trim(),
386
- username: $("#cloudUsername").value.trim(),
387
- password: $("#cloudPassword").value,
468
+ username: $("#authUsername").value.trim(),
469
+ password: $("#authPassword").value,
388
470
  };
389
471
  }
390
472
 
391
473
  async function handleCloudRegister() {
392
474
  try {
393
- setStatus("正在注册同步账号 ...", "info");
394
- const payload = getCloudFormPayload();
475
+ setAuthStatus("正在注册同步账号 ...", "info");
476
+ const payload = getAuthFormPayload();
395
477
  const result = await bridge.registerCloudAccount(payload);
396
- $("#cloudPassword").value = "";
478
+ $("#authPassword").value = "";
479
+ state.cloud = result;
480
+ renderCloudStatus();
481
+ applyAccessState(true);
397
482
  await refreshDashboard(false);
398
483
  setStatus(result.message || "账号注册成功。", "success");
399
484
  } catch (error) {
400
- setStatus(`注册失败:${error.message}`, "error");
485
+ setAuthStatus(`注册失败:${error.message}`, "error");
401
486
  }
402
487
  }
403
488
 
404
489
  async function handleCloudLogin() {
405
490
  try {
406
- setStatus("正在登录同步账号 ...", "info");
407
- const payload = getCloudFormPayload();
491
+ setAuthStatus("正在登录同步账号 ...", "info");
492
+ const payload = getAuthFormPayload();
408
493
  const result = await bridge.loginCloudAccount(payload);
409
- $("#cloudPassword").value = "";
494
+ $("#authPassword").value = "";
495
+ state.cloud = result;
496
+ renderCloudStatus();
497
+ applyAccessState(true);
410
498
  await refreshDashboard(false);
411
499
  setStatus(result.message || "账号登录成功。", "success");
412
500
  } catch (error) {
413
- setStatus(`登录失败:${error.message}`, "error");
501
+ setAuthStatus(`登录失败:${error.message}`, "error");
414
502
  }
415
503
  }
416
504
 
@@ -418,10 +506,13 @@ async function handleCloudLogout() {
418
506
  try {
419
507
  setStatus("正在退出同步账号 ...", "info");
420
508
  const result = await bridge.logoutCloudAccount();
421
- await refreshDashboard(false);
422
- setStatus(result.message || "已退出同步账号。", "success");
509
+ $("#authPassword").value = "";
510
+ state.cloud = result;
511
+ renderCloudStatus();
512
+ applyAccessState(false);
513
+ setAuthStatus(result.message || "已退出同步账号。", "success");
423
514
  } catch (error) {
424
- setStatus(`退出失败:${error.message}`, "error");
515
+ setAuthStatus(`退出失败:${error.message}`, "error");
425
516
  }
426
517
  }
427
518
 
@@ -435,6 +526,11 @@ async function handleCloudPush() {
435
526
  "success",
436
527
  );
437
528
  } catch (error) {
529
+ if (isUnauthorizedError(error)) {
530
+ await handleAccessRevoked(error.message);
531
+ return;
532
+ }
533
+
438
534
  setStatus(`推送失败:${error.message}`, "error");
439
535
  }
440
536
  }
@@ -461,6 +557,11 @@ async function handleCloudPull(mode) {
461
557
  "success",
462
558
  );
463
559
  } catch (error) {
560
+ if (isUnauthorizedError(error)) {
561
+ await handleAccessRevoked(error.message);
562
+ return;
563
+ }
564
+
464
565
  setStatus(`拉取失败:${error.message}`, "error");
465
566
  }
466
567
  }
@@ -480,15 +581,20 @@ async function handleSubmitEndpoint(event) {
480
581
  try {
481
582
  if (id) {
482
583
  await bridge.updateEndpoint({ id, note, url, key });
483
- setStatus(`已更新连接:${note}`, "success");
484
584
  } else {
485
585
  await bridge.createEndpoint({ note, url, key });
486
- setStatus(`已新增连接:${note}`, "success");
487
586
  }
488
587
 
489
- resetForm();
490
588
  await refreshDashboard(false);
589
+ closeEndpointModal();
590
+ resetForm();
591
+ setStatus(id ? `已更新连接:${note}` : `已新增连接:${note}`, "success");
491
592
  } catch (error) {
593
+ if (isUnauthorizedError(error)) {
594
+ await handleAccessRevoked(error.message);
595
+ return;
596
+ }
597
+
492
598
  setStatus(`保存失败:${error.message}`, "error");
493
599
  }
494
600
  }
@@ -502,11 +608,17 @@ async function handleDeleteEndpoint(id, note) {
502
608
  try {
503
609
  await bridge.deleteEndpoint({ id });
504
610
  if ($("#endpointId").value.trim() === id) {
611
+ closeEndpointModal();
505
612
  resetForm();
506
613
  }
507
614
  await refreshDashboard(false);
508
615
  setStatus(`已删除连接:${note}`, "success");
509
616
  } catch (error) {
617
+ if (isUnauthorizedError(error)) {
618
+ await handleAccessRevoked(error.message);
619
+ return;
620
+ }
621
+
510
622
  setStatus(`删除失败:${error.message}`, "error");
511
623
  }
512
624
  }
@@ -520,23 +632,45 @@ async function handleOpenPath(targetPath, label) {
520
632
 
521
633
  setStatus(`已打开${label}。`, "success");
522
634
  } catch (error) {
635
+ if (isUnauthorizedError(error)) {
636
+ await handleAccessRevoked(error.message);
637
+ return;
638
+ }
639
+
523
640
  setStatus(`打开${label}失败:${error.message}`, "error");
524
641
  }
525
642
  }
526
643
 
527
644
  function bindEvents() {
645
+ $("#authForm").addEventListener("submit", (event) => {
646
+ event.preventDefault();
647
+ handleCloudLogin();
648
+ });
649
+ $("#authRegisterButton").addEventListener("click", () => {
650
+ handleCloudRegister();
651
+ });
528
652
  $("#refreshButton").addEventListener("click", () => {
529
653
  refreshDashboard();
530
654
  });
655
+ $("#openCreateEndpointButton").addEventListener("click", () => {
656
+ resetForm();
657
+ openEndpointModal();
658
+ });
659
+ $("#closeEndpointModalButton").addEventListener("click", () => {
660
+ closeEndpointModal();
661
+ });
662
+ $("#endpointModal").addEventListener("click", (event) => {
663
+ if (event.target.dataset.modalClose === "true") {
664
+ closeEndpointModal();
665
+ }
666
+ });
531
667
  $("#endpointForm").addEventListener("submit", handleSubmitEndpoint);
532
668
  $("#clearFormButton").addEventListener("click", () => {
533
669
  resetForm();
534
- setStatus("已清空表单。", "info");
670
+ setStatus("已重置表单,可作为新连接保存。", "info");
535
671
  });
536
- $("#enableProxyModeButton").addEventListener("click", handleEnableProxyMode);
537
- $("#cloudRegisterButton").addEventListener("click", handleCloudRegister);
538
- $("#cloudLoginButton").addEventListener("click", handleCloudLogin);
539
672
  $("#cloudLogoutButton").addEventListener("click", handleCloudLogout);
673
+ $("#openSwitchAccountButton").addEventListener("click", handleCloudLogout);
540
674
  $("#cloudPushButton").addEventListener("click", handleCloudPush);
541
675
  $("#cloudPullMergeButton").addEventListener("click", () => {
542
676
  handleCloudPull("merge");
@@ -554,11 +688,15 @@ function bindEvents() {
554
688
  handleOpenPath(state.paths.endpointStorePath, " 连接数据文件");
555
689
  }
556
690
  });
691
+ window.addEventListener("keydown", (event) => {
692
+ if (event.key === "Escape" && !$("#endpointModal").hidden) {
693
+ closeEndpointModal();
694
+ }
695
+ });
557
696
  }
558
697
 
559
698
  window.addEventListener("DOMContentLoaded", async () => {
560
699
  bindEvents();
561
700
  resetForm();
562
- await refreshDashboard(false);
563
- setStatus("应用已就绪,你可以直接新增 URL / Key / 备注 并切换。", "success");
701
+ await bootstrapConsoleAccess();
564
702
  });