fix(desktop): clarify local data reset errors
This commit is contained in:
1 parent
bd6d030239
commit
9104d30bea
7 files changed
+97
-24
No files matched your search
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
>
|
||||
|
||||
Vendored
+10
-1
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1206,7 +1206,12 @@ export const zhCN = {
|
||||
warning: "此操作无法撤销。尚未同步的笔记和更改将永久丢失。",
|
||||
confirm: "清除本地数据",
|
||||
clearing: "正在清除...",
|
||||
error: "无法清除本地数据,请重试。",
|
||||
errors: {
|
||||
unsafeDataDirectory: "无法确认本地数据目录可以安全删除,尚未更改任何数据。",
|
||||
applicationBundleNotFound: "无法识别清除后需要重新启动的已安装应用。请将 EdgeEver 重新安装到“应用程序”后再试。",
|
||||
helperStartFailed: "无法启动 macOS 数据清理助手,尚未更改任何数据。请重新启动应用后再试。",
|
||||
unexpected: "清除本地数据前发生意外错误,尚未更改任何数据。请重新启动应用后再试。",
|
||||
},
|
||||
},
|
||||
loginDevices: {
|
||||
title: "登录设备",
|
||||
|
||||
Reference in new issue
Block a user