diff --git a/src/hooks/use-global-obj-loader.ts b/src/hooks/use-global-obj-loader.ts index 69422095..59a5e9f9 100644 --- a/src/hooks/use-global-obj-loader.ts +++ b/src/hooks/use-global-obj-loader.ts @@ -28,7 +28,7 @@ export function useGlobalObjLoader( useEffect(() => { if (!url) return - const cleanUrl = url.replace(/&cachebust_origin=$/, "") + const cleanUrl = normalizeObjLoaderCacheUrl(url) const cache = window.TSCIRCUIT_OBJ_LOADER_CACHE let hasUrlChanged = false @@ -120,3 +120,32 @@ export function useGlobalObjLoader( return obj } + +export function normalizeObjLoaderCacheUrl(url: string): string { + try { + const hasProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url) + const parsedUrl = new URL( + url, + globalThis.location?.href ?? "https://tscircuit.local", + ) + parsedUrl.searchParams.delete("cachebust_origin") + + const sortedParams = new URLSearchParams( + Array.from(parsedUrl.searchParams.entries()).sort(([a], [b]) => + a.localeCompare(b), + ), + ) + parsedUrl.search = sortedParams.toString() + + if (!hasProtocol) { + return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}` + } + + return parsedUrl.toString() + } catch { + return url + .replace(/([?&])cachebust_origin=[^&]*/g, "$1") + .replace(/[?&]$/, "") + .replace("?&", "?") + } +} diff --git a/tests/normalize-obj-loader-cache-url.test.ts b/tests/normalize-obj-loader-cache-url.test.ts new file mode 100644 index 00000000..a00e7e2a --- /dev/null +++ b/tests/normalize-obj-loader-cache-url.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test" +import { normalizeObjLoaderCacheUrl } from "../src/hooks/use-global-obj-loader" + +test("removes cachebust_origin from remote EasyEDA model URLs", () => { + const url = + "https://modelcdn.tscircuit.com/easyeda_models/download?uuid=c886ec2b42464573a88fc1f647577a49&pn=C5184526&cachebust_origin=https%3A%2F%2Ftscircuit.com" + + expect(normalizeObjLoaderCacheUrl(url)).toBe( + "https://modelcdn.tscircuit.com/easyeda_models/download?pn=C5184526&uuid=c886ec2b42464573a88fc1f647577a49", + ) +}) + +test("normalizes cache-equivalent model URLs to the same key", () => { + const first = + "https://modelcdn.tscircuit.com/easyeda_models/download?uuid=abc&pn=C1&cachebust_origin=https%3A%2F%2Ftscircuit.com" + const second = + "https://modelcdn.tscircuit.com/easyeda_models/download?cachebust_origin=http%3A%2F%2Flocalhost%3A3020&pn=C1&uuid=abc" + + expect(normalizeObjLoaderCacheUrl(first)).toBe( + normalizeObjLoaderCacheUrl(second), + ) +}) + +test("preserves relative model URLs without cachebust params", () => { + expect(normalizeObjLoaderCacheUrl("/easyeda-models/part.obj")).toBe( + "/easyeda-models/part.obj", + ) +})