Files
cc-switch/src/main.tsx
Jason 4afa68eac6 fix: prevent silent config fallback and data loss on startup
This commit introduces fail-fast error handling for config loading failures,
replacing the previous silent fallback to default config which could cause
data loss (e.g., all user providers disappearing).

Key changes:

Backend (Rust):
- Replace AppState::new() with AppState::try_new() to explicitly propagate errors
- Remove Default trait to prevent accidental empty state creation
- Add init_status module as global error cache (OnceLock + RwLock)
- Implement dual-channel error notification:
  1. Event emission (low-latency, may race with frontend subscription)
  2. Command-based polling (reliable, guaranteed delivery)
- Remove unconditional save on startup to prevent overwriting corrupted config

Frontend (TypeScript):
- Add event listener for "configLoadError" (fast path)
- Add bootstrap-time polling via get_init_error command (reliable path)
- Display detailed error dialog with recovery instructions
- Prompt user to exit for manual repair

Impact:
- First-time users: No change (load() returns Ok(default) when file missing)
- Corrupted config: Application shows error and exits gracefully
- Prevents accidental config overwrite during initialization

Fixes the only critical issue identified in previous code review (silent
fallback causing data loss).
2025-11-03 22:33:10 +08:00

99 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { UpdateProvider } from "./contexts/UpdateContext";
import "./index.css";
// 导入国际化配置
import "./i18n";
import { QueryClientProvider } from "@tanstack/react-query";
import { ThemeProvider } from "@/components/theme-provider";
import { queryClient } from "@/lib/query";
import { Toaster } from "@/components/ui/sonner";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { confirm, message } from "@tauri-apps/plugin-dialog";
import { exit } from "@tauri-apps/plugin-process";
// 根据平台添加 body class便于平台特定样式
try {
const ua = navigator.userAgent || "";
const plat = (navigator.platform || "").toLowerCase();
const isMac = /mac/i.test(ua) || plat.includes("mac");
if (isMac) {
document.body.classList.add("is-mac");
}
} catch {
// 忽略平台检测失败
}
// 监听后端的配置加载错误事件:仅提醒用户并在确认后退出,不修改任何配置文件
try {
void listen("configLoadError", async (evt) => {
const payload = evt.payload as { path?: string; error?: string } | null;
const path = payload?.path ?? "~/.cc-switch/config.json";
const detail = payload?.error ?? "Unknown error";
await message(
`无法读取配置文件:\n${path}\n\n错误详情\n${detail}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak恢复。`,
{ title: "配置加载失败", kind: "error" },
);
const shouldExit = await confirm("现在退出应用以进行修复?", {
title: "退出确认",
okLabel: "退出应用",
cancelLabel: "取消",
});
if (shouldExit) {
await exit(1);
}
});
} catch (e) {
// 忽略事件订阅异常(例如在非 Tauri 环境下)
console.error("订阅 configLoadError 事件失败", e);
}
async function bootstrap() {
// 启动早期主动查询后端初始化错误,避免事件竞态
try {
const initError = (await invoke("get_init_error")) as
| { path?: string; error?: string }
| null;
if (initError && (initError.path || initError.error)) {
const path = initError.path ?? "~/.cc-switch/config.json";
const detail = initError.error ?? "Unknown error";
await message(
`无法读取配置文件:\n${path}\n\n错误详情\n${detail}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak恢复。`,
{ title: "配置加载失败", kind: "error" },
);
const shouldExit = await confirm("现在退出应用以进行修复?", {
title: "退出确认",
okLabel: "退出应用",
cancelLabel: "取消",
});
if (shouldExit) {
await exit(1);
return; // 退出流程
}
}
} catch (e) {
// 忽略拉取错误,继续渲染
console.error("拉取初始化错误失败", e);
}
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider defaultTheme="system" storageKey="cc-switch-theme">
<UpdateProvider>
<App />
<Toaster />
</UpdateProvider>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>,
);
}
void bootstrap();