fix: skip a stale MakerWorld defaultInstanceId when picking the print profile (#15)

MakerWorld can leave a design's defaultInstanceId pointing at a print profile that no longer
exists. pickMakerworldInstanceId (extension) and makerworldInstanceIdFromNextData (backend)
returned it unchecked, so the download-link request went to the dead profile and MakerWorld
answered 403 "The client does not have access rights to the content." Use the default only when
it is still one of the design's instances, otherwise the first, as selectMakerworldProfiles
already does.

Fixes #14


Claude-Session: https://claude.ai/code/session_01KGCb65Zhzwd3daF862ZE6R

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paul TrainaandClaude Opus 5.5 authored and GitHub committed 2026-10-13 06:47:04 +02:00
1 parent 76907212d4
commit d5ae80951c
3 files changed
+42 -7

No files matched your search

+5 -3
View File
@@ -536,13 +536,15 @@ export function extractPageMetadata(html: string, pageHost: string): ImportedPag
return meta;
}
/** The profile named in the URL hash (if the design has it), then the default, then the first. */
function makerworldInstanceIdFromNextData(data: unknown, requestedInstanceId: string | null): string | null {
/** The profile named in the URL hash (if the design has it), then the default (if the design still
* has it), then the first. MakerWorld can leave `defaultInstanceId` pointing at a deleted profile,
* whose download answers 403 "The client does not have access rights to the content." */
export function makerworldInstanceIdFromNextData(data: unknown, requestedInstanceId: string | null): string | null {
const design = getPath(data, "props", "pageProps", "design") as Record<string, unknown> | undefined;
if (!design) return null;
if (requestedInstanceId && makerworldDesignHasInstance(design, requestedInstanceId)) return requestedInstanceId;
const defaultInstance = design.defaultInstanceId;
if (defaultInstance) return String(defaultInstance);
if (defaultInstance && makerworldDesignHasInstance(design, String(defaultInstance))) return String(defaultInstance);
const instances = design.instances;
if (Array.isArray(instances)) {
for (const inst of instances) {
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { makerworldInstanceIdFromNextData } from "../src/services/importResolvers";
function nextData(design: Record<string, unknown>) {
return { props: { pageProps: { design } } };
}
describe("makerworldInstanceIdFromNextData", () => {
const design = { defaultInstanceId: 2, instances: [{ id: 1 }, { id: 2 }, { id: 3 }] };
it("takes the link's profile, else the default, else the first", () => {
expect(makerworldInstanceIdFromNextData(nextData(design), "3")).toBe("3");
expect(makerworldInstanceIdFromNextData(nextData(design), null)).toBe("2");
expect(makerworldInstanceIdFromNextData(nextData(design), "999")).toBe("2");
expect(makerworldInstanceIdFromNextData(nextData({ instances: [{ id: 7 }] }), null)).toBe("7");
});
// Seen on MakerWorld model 1106505: defaultInstanceId 1102432, but the only profile is 1113369.
// Downloading the stale default answers 403 "The client does not have access rights to the content."
it("skips a default that is no longer one of the design's profiles", () => {
const stale = { defaultInstanceId: 1102432, instances: [{ id: 1113369 }] };
expect(makerworldInstanceIdFromNextData(nextData(stale), null)).toBe("1113369");
});
it("answers null without a design or profiles", () => {
expect(makerworldInstanceIdFromNextData({}, null)).toBeNull();
expect(makerworldInstanceIdFromNextData(nextData({ defaultInstanceId: 5, instances: [] }), null)).toBeNull();
});
});
+8 -4
View File
@@ -100,12 +100,16 @@ export async function loadMakerworldCollectionTotal(pageUrl: string): Promise<nu
);
}
/** The requested profile (if the design has it), then the default, then the first. */
/** The requested profile (if the design has it), then the default (if the design still has it),
* then the first. MakerWorld can leave `defaultInstanceId` pointing at a profile that was since
* deleted, and asking for that one's download answers 403 "The client does not have access rights
* to the content." */
export function pickMakerworldInstanceId(design: MakerworldDesign, requestedInstanceId: string | null): string | null {
const instances = Array.isArray(design.instances) ? design.instances.filter((inst) => inst && inst.id) : [];
if (requestedInstanceId && instances.some((inst) => String(inst.id) === requestedInstanceId))
return requestedInstanceId;
if (design.defaultInstanceId) return String(design.defaultInstanceId);
const has = (id: string) => instances.some((inst) => String(inst.id) === id);
if (requestedInstanceId && has(requestedInstanceId)) return requestedInstanceId;
const defaultId = design.defaultInstanceId ? String(design.defaultInstanceId) : null;
if (defaultId && has(defaultId)) return defaultId;
return instances.length ? String(instances[0].id) : null;
}