clean review nits: typed prefix sentinel, instanceof narrowing, drop dead re-export

- Replace anonymous Error in resolveImageWallpaperUrl with typed
  UnsafeImagePrefixError, mirroring UnsafeAssetPathError so cause
  chains stay discriminable.
- Replace `(err as BackgroundLoadError).cause` casts in wallpaper
  tests with instanceof narrowing (no `as` per project rules).
- Remove unused `WALLPAPER_PATHS` re-export from projectPersistence;
  consumers import directly from @/lib/wallpaper (SSOT).
This commit is contained in:
Enriquefft
2026-04-24 22:34:00 -05:00
parent 373319808e
commit e06e40dbc2
3 changed files with 23 additions and 14 deletions
@@ -56,8 +56,6 @@ function normalizeWallpaperValue(value: string): string {
return CANONICAL_WALLPAPERS.has(canonical) ? canonical : DEFAULT_WALLPAPER;
}
export { WALLPAPER_PATHS };
export const PROJECT_VERSION = 2;
export interface ProjectEditorState {
+15 -8
View File
@@ -5,6 +5,7 @@ import {
classifyWallpaper,
DEFAULT_WALLPAPER,
resolveImageWallpaperUrl,
UnsafeImagePrefixError,
WALLPAPER_COUNT,
WALLPAPER_PATHS,
} from "./wallpaper";
@@ -170,8 +171,14 @@ describe("resolveImageWallpaperUrl", () => {
expect(resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe("/wallpapers/my%20image.jpg");
});
it("rejects image paths outside /wallpapers/", () => {
expect(() => resolveImageWallpaperUrl("/etc/passwd")).toThrow(BackgroundLoadError);
it("rejects image paths outside /wallpapers/ with UnsafeImagePrefixError as cause", () => {
try {
resolveImageWallpaperUrl("/etc/passwd");
expect.fail("should have thrown");
} catch (err) {
if (!(err instanceof BackgroundLoadError)) throw err;
expect(err.cause).toBeInstanceOf(UnsafeImagePrefixError);
}
});
it("wraps traversal attempts in BackgroundLoadError (preserves UnsafeAssetPathError as cause)", () => {
@@ -179,8 +186,8 @@ describe("resolveImageWallpaperUrl", () => {
resolveImageWallpaperUrl("/wallpapers/../etc/passwd");
expect.fail("should have thrown");
} catch (err) {
expect(err).toBeInstanceOf(BackgroundLoadError);
expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError);
if (!(err instanceof BackgroundLoadError)) throw err;
expect(err.cause).toBeInstanceOf(UnsafeAssetPathError);
}
});
@@ -189,8 +196,8 @@ describe("resolveImageWallpaperUrl", () => {
resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar");
expect.fail("should have thrown");
} catch (err) {
expect(err).toBeInstanceOf(BackgroundLoadError);
expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError);
if (!(err instanceof BackgroundLoadError)) throw err;
expect(err.cause).toBeInstanceOf(UnsafeAssetPathError);
}
});
@@ -226,8 +233,8 @@ describe("resolveImageWallpaperUrl", () => {
resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg");
expect.fail("should have thrown");
} catch (err) {
expect(err).toBeInstanceOf(BackgroundLoadError);
expect((err as BackgroundLoadError).cause).toBeInstanceOf(AssetBaseUnavailableError);
if (!(err instanceof BackgroundLoadError)) throw err;
expect(err.cause).toBeInstanceOf(AssetBaseUnavailableError);
}
});
});
+8 -4
View File
@@ -37,6 +37,13 @@ export function classifyWallpaper(value: string): WallpaperClassification {
const ALLOWED_IMAGE_PREFIX = "/wallpapers/";
export class UnsafeImagePrefixError extends Error {
constructor(prefix: string) {
super(`Image wallpaper path must live under ${prefix}`);
this.name = "UnsafeImagePrefixError";
}
}
export function resolveImageWallpaperUrl(imagePath: string): string {
if (
imagePath.startsWith("http://") ||
@@ -48,10 +55,7 @@ export function resolveImageWallpaperUrl(imagePath: string): string {
}
const withLeadingSlash = imagePath.startsWith("/") ? imagePath : `/${imagePath}`;
if (!withLeadingSlash.startsWith(ALLOWED_IMAGE_PREFIX)) {
throw new BackgroundLoadError(
imagePath,
new Error(`Image wallpaper path must live under ${ALLOWED_IMAGE_PREFIX}`),
);
throw new BackgroundLoadError(imagePath, new UnsafeImagePrefixError(ALLOWED_IMAGE_PREFIX));
}
try {
return getAssetPath(withLeadingSlash.slice(1));