fix(desktop): clarify local data reset errors

This commit is contained in:
tianma-if committed 2026-08-18 15:12:02 +08:00
1 parent bd6d030239
commit 9104d30bea
7 files changed
+97 -24

No files matched your search

+7 -2
View File
@@ -23,7 +23,7 @@ import { isAllowedPrintPreviewUrl } from "./window-open-policy.mjs";
import { showWindow } from "./window-visibility.mjs";
import { trayIconPath } from "./tray-icon.mjs";
import { writeRichClipboard } from "./clipboard-write.mjs";
import { scheduleMacLocalDataReset } from "./local-data-reset.mjs";
import { LocalDataResetError, scheduleMacLocalDataReset } from "./local-data-reset.mjs";
import { buildDesktopDiagnosticIssueUrl, normalizeDesktopDiagnostic } from "./desktop-diagnostics.mjs";
import electronUpdater from "electron-updater";
@@ -828,9 +828,14 @@ app.whenReady().then(async () => {
});
} catch (error) {
await writeDiagnostic("local-data-reset.schedule-failed", {
code: error instanceof LocalDataResetError ? error.code : "unexpected",
message: error instanceof Error ? error.message : String(error),
cause: error instanceof LocalDataResetError && error.cause instanceof Error ? error.cause.message : undefined,
});
throw error;
return {
scheduled: false,
errorCode: error instanceof LocalDataResetError ? error.code : "unexpected",
};
}
// Only begin shutting down once the detached reset helper has definitely
+28 -6
View File
@@ -22,6 +22,14 @@ if /bin/rm -rf -- "$user_data_dir"; then
fi
`;
export class LocalDataResetError extends Error {
constructor(code, message, options) {
super(message, options);
this.name = "LocalDataResetError";
this.code = code;
}
}
export const managedUserDataDirectory = (userDataDirectory, appDataDirectory) => {
const target = resolve(userDataDirectory);
const appData = resolve(appDataDirectory);
@@ -33,7 +41,10 @@ export const managedUserDataDirectory = (userDataDirectory, appDataDirectory) =>
isAbsolute(relativeTarget) ||
relativeTarget.includes(sep)
) {
throw new Error("EdgeEver local data must be a direct child of the application-data directory");
throw new LocalDataResetError(
"unsafe-data-directory",
"EdgeEver local data must be a direct child of the application-data directory",
);
}
return target;
};
@@ -46,7 +57,10 @@ export const macApplicationBundlePath = (executablePath) => {
if (parent === candidate) break;
candidate = parent;
}
throw new Error("EdgeEver must be running from a macOS application bundle");
throw new LocalDataResetError(
"application-bundle-not-found",
"EdgeEver must be running from a macOS application bundle",
);
};
export const scheduleMacLocalDataReset = async ({
@@ -73,10 +87,18 @@ export const scheduleMacLocalDataReset = async ({
stdio: "ignore",
},
);
await new Promise((resolveSpawn, rejectSpawn) => {
helper.once("spawn", resolveSpawn);
helper.once("error", rejectSpawn);
});
try {
await new Promise((resolveSpawn, rejectSpawn) => {
helper.once("spawn", resolveSpawn);
helper.once("error", rejectSpawn);
});
} catch (error) {
throw new LocalDataResetError(
"helper-start-failed",
"EdgeEver could not start the local-data reset helper",
{ cause: error },
);
}
helper.unref();
return { applicationPath, target };
};
@@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test";
import { EventEmitter } from "node:events";
import {
LocalDataResetError,
macApplicationBundlePath,
managedUserDataDirectory,
scheduleMacLocalDataReset,
@@ -21,16 +22,28 @@ describe("desktop local data reset", () => {
"/Users/example/Library/Application Support/EdgeEver/accounts",
"/Users/example/Library/Application Support",
)).toThrow();
expect(() => managedUserDataDirectory(
"/Users/example/Documents",
"/Users/example/Library/Application Support",
)).toThrow();
try {
managedUserDataDirectory(
"/Users/example/Documents",
"/Users/example/Library/Application Support",
);
throw new Error("expected path validation to fail");
} catch (error) {
expect(error).toBeInstanceOf(LocalDataResetError);
expect(error.code).toBe("unsafe-data-directory");
}
});
test("finds the enclosing macOS application bundle", () => {
expect(macApplicationBundlePath("/Applications/EdgeEver.app/Contents/MacOS/EdgeEver"))
.toBe("/Applications/EdgeEver.app");
expect(() => macApplicationBundlePath("/usr/local/bin/edgeever")).toThrow();
try {
macApplicationBundlePath("/usr/local/bin/edgeever");
throw new Error("expected bundle validation to fail");
} catch (error) {
expect(error).toBeInstanceOf(LocalDataResetError);
expect(error.code).toBe("application-bundle-not-found");
}
});
test("passes paths as shell arguments instead of interpolating them", async () => {
@@ -81,6 +94,9 @@ describe("desktop local data reset", () => {
userDataDirectory: "/Users/example/Library/Application Support/EdgeEver",
});
await expect(resultPromise).rejects.toThrow("spawn failed");
const error = await resultPromise.catch((reason) => reason);
expect(error).toBeInstanceOf(LocalDataResetError);
expect(error.code).toBe("helper-start-failed");
expect(error.cause?.message).toBe("spawn failed");
});
});
@@ -12,23 +12,34 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
const localDataResetErrorKeys: Record<DesktopLocalDataResetErrorCode, string> = {
"unsafe-data-directory": "localDataReset.errors.unsafeDataDirectory",
"application-bundle-not-found": "localDataReset.errors.applicationBundleNotFound",
"helper-start-failed": "localDataReset.errors.helperStartFailed",
unexpected: "localDataReset.errors.unexpected",
};
export const DesktopLocalDataCard = () => {
const { t } = useTranslation();
const bridge = window.edgeeverDesktop;
const [confirmationOpen, setConfirmationOpen] = useState(false);
const [isClearing, setIsClearing] = useState(false);
const [error, setError] = useState(false);
const [errorCode, setErrorCode] = useState<DesktopLocalDataResetErrorCode | null>(null);
if (!bridge?.canClearLocalData) return null;
const handleClear = async () => {
setIsClearing(true);
setError(false);
setErrorCode(null);
try {
await bridge.clearLocalData();
const result = await bridge.clearLocalData();
if (!result.scheduled) {
setIsClearing(false);
setErrorCode(result.errorCode);
}
} catch {
setIsClearing(false);
setError(true);
setErrorCode("unexpected");
}
};
@@ -43,10 +54,10 @@ export const DesktopLocalDataCard = () => {
<CardDescription className="text-xs leading-5">{t("localDataReset.description")}</CardDescription>
</CardHeader>
<CardContent className="grid gap-3 p-4 pt-0 sm:px-5 sm:pb-5">
{error ? (
{errorCode ? (
<p className="flex items-center gap-1.5 text-xs text-rose-700" role="alert">
<AlertCircle className="h-3.5 w-3.5 shrink-0" />
{t("localDataReset.error")}
{t(localDataResetErrorKeys[errorCode])}
</p>
) : null}
<div>
@@ -55,7 +66,7 @@ export const DesktopLocalDataCard = () => {
variant="danger"
className="bg-rose-600 font-semibold text-white shadow-sm hover:bg-rose-700"
onClick={() => {
setError(false);
setErrorCode(null);
setConfirmationOpen(true);
}}
>
+10 -1
View File
@@ -19,7 +19,10 @@ interface EdgeEverDesktopBridge {
copyHtml(html: string, plainText: string): Promise<boolean>;
setSessionToken(value: string): Promise<{ stored: boolean }>;
clearSessionToken(): Promise<{ stored: false }>;
clearLocalData(): Promise<{ scheduled: true }>;
clearLocalData(): Promise<
{ scheduled: true }
| { scheduled: false; errorCode: DesktopLocalDataResetErrorCode }
>;
recordRendererError(details: DesktopRendererErrorDetails): Promise<{ recorded: true }>;
openRendererIssue(details: DesktopRendererErrorDetails): Promise<{ opened: true }>;
sidecarStatus(): Promise<{ available: boolean; path: string; scope: string }>;
@@ -37,6 +40,12 @@ interface EdgeEverDesktopBridge {
onImportMarkdown(callback: (payload: { name: string; content: string }) => void): () => void;
}
type DesktopLocalDataResetErrorCode =
| "unsafe-data-directory"
| "application-bundle-not-found"
| "helper-start-failed"
| "unexpected";
interface DesktopRendererErrorDetails {
kind: string;
message?: string;
+6 -1
View File
@@ -1206,7 +1206,12 @@ export const enUS = {
warning: "This action cannot be undone. Any notes or changes that have not been synced will be permanently lost.",
confirm: "Clear local data",
clearing: "Clearing...",
error: "Local data could not be cleared. Please try again.",
errors: {
unsafeDataDirectory: "EdgeEver could not verify that the local data folder is safe to remove. Your data was not changed.",
applicationBundleNotFound: "EdgeEver could not identify the installed app needed to restart after clearing. Reinstall EdgeEver in Applications and try again.",
helperStartFailed: "EdgeEver could not start the macOS data-clearing helper. Your data was not changed. Restart the app and try again.",
unexpected: "An unexpected error occurred before local data could be cleared. Your data was not changed. Restart the app and try again.",
},
},
loginDevices: {
title: "Signed-in devices",
+6 -1
View File
@@ -1206,7 +1206,12 @@ export const zhCN = {
warning: "此操作无法撤销。尚未同步的笔记和更改将永久丢失。",
confirm: "清除本地数据",
clearing: "正在清除...",
error: "无法清除本地数据,请重试。",
errors: {
unsafeDataDirectory: "无法确认本地数据目录可以安全删除,尚未更改任何数据。",
applicationBundleNotFound: "无法识别清除后需要重新启动的已安装应用。请将 EdgeEver 重新安装到“应用程序”后再试。",
helperStartFailed: "无法启动 macOS 数据清理助手,尚未更改任何数据。请重新启动应用后再试。",
unexpected: "清除本地数据前发生意外错误,尚未更改任何数据。请重新启动应用后再试。",
},
},
loginDevices: {
title: "登录设备",