fix: harden publisher OG profile images

This commit is contained in:
Jesse Merhi
2026-06-28 02:01:12 +10:00
committed by GitHub
parent 18dac6900f
commit bdb23c3a9f
19 changed files with 1045 additions and 90 deletions
+6 -2
View File
@@ -91,6 +91,7 @@ import type * as lib_packageRegistry from "../lib/packageRegistry.js";
import type * as lib_packageSearchDigest from "../lib/packageSearchDigest.js";
import type * as lib_packageSecurity from "../lib/packageSecurity.js";
import type * as lib_public from "../lib/public.js";
import type * as lib_publicBrowse from "../lib/publicBrowse.js";
import type * as lib_publicRouteReservations from "../lib/publicRouteReservations.js";
import type * as lib_publishLimits from "../lib/publishLimits.js";
import type * as lib_publisherAbuseScoring from "../lib/publisherAbuseScoring.js";
@@ -126,14 +127,15 @@ import type * as lib_tokens from "../lib/tokens.js";
import type * as lib_userSearch from "../lib/userSearch.js";
import type * as lib_userSkillStats from "../lib/userSkillStats.js";
import type * as lib_webhooks from "../lib/webhooks.js";
import type * as lib_workerTextRedaction from "../lib/workerTextRedaction.js";
import type * as maintenance from "../maintenance.js";
import type * as managementDevSeed from "../managementDevSeed.js";
import type * as migrations from "../migrations.js";
import type * as packageInspectorHttp from "../packageInspectorHttp.js";
import type * as packageInspectorNode from "../packageInspectorNode.js";
import type * as packageLeaderboards from "../packageLeaderboards.js";
import type * as packagePublishTokens from "../packagePublishTokens.js";
import type * as packages from "../packages.js";
import type * as packageLeaderboards from "../packageLeaderboards.js";
import type * as publisherAbuse from "../publisherAbuse.js";
import type * as publisherAbuseDevSeed from "../publisherAbuseDevSeed.js";
import type * as publishers from "../publishers.js";
@@ -246,6 +248,7 @@ declare const fullApi: ApiFromModules<{
"lib/packageSearchDigest": typeof lib_packageSearchDigest;
"lib/packageSecurity": typeof lib_packageSecurity;
"lib/public": typeof lib_public;
"lib/publicBrowse": typeof lib_publicBrowse;
"lib/publicRouteReservations": typeof lib_publicRouteReservations;
"lib/publishLimits": typeof lib_publishLimits;
"lib/publisherAbuseScoring": typeof lib_publisherAbuseScoring;
@@ -281,14 +284,15 @@ declare const fullApi: ApiFromModules<{
"lib/userSearch": typeof lib_userSearch;
"lib/userSkillStats": typeof lib_userSkillStats;
"lib/webhooks": typeof lib_webhooks;
"lib/workerTextRedaction": typeof lib_workerTextRedaction;
maintenance: typeof maintenance;
managementDevSeed: typeof managementDevSeed;
migrations: typeof migrations;
packageInspectorHttp: typeof packageInspectorHttp;
packageInspectorNode: typeof packageInspectorNode;
packageLeaderboards: typeof packageLeaderboards;
packagePublishTokens: typeof packagePublishTokens;
packages: typeof packages;
packageLeaderboards: typeof packageLeaderboards;
publisherAbuse: typeof publisherAbuse;
publisherAbuseDevSeed: typeof publisherAbuseDevSeed;
publishers: typeof publishers;
+316 -11
View File
@@ -17,6 +17,7 @@ import {
getProfileByHandle,
createMemberInvite,
declineMemberInvite,
getOgMetaByHandle,
listMembers,
listPublishedPage,
listStarredPage,
@@ -303,6 +304,24 @@ const getProfileByHandleHandler = (
getProfileByHandle as unknown as WrappedHandler<{ handle: string }>
)._handler;
const getOgMetaByHandleHandler = (
getOgMetaByHandle as unknown as WrappedHandler<
{ handle: string },
{
displayName?: string | null;
affiliations?: Array<{
publisher?: {
handle?: string | null;
displayName?: string | null;
image?: string | null;
} | null;
role?: string;
}>;
stats: { downloads: number; installs: number; stars: number };
} | null
>
)._handler;
const getPublishedDisplayManifestHandler = (
getPublishedDisplayManifest as unknown as WrappedHandler<
{
@@ -451,22 +470,30 @@ const resolvePublishTargetForUserInternalHandler = (
)._handler;
function indexedRows(rows: unknown[]) {
const paginate = vi.fn(
async ({ cursor, numItems }: { cursor: string | null; numItems: number }) => {
const offset = cursor ? Number(cursor) : 0;
const page = rows.slice(offset, offset + numItems);
const nextOffset = offset + page.length;
const isDone = nextOffset >= rows.length;
return {
page,
isDone,
continueCursor: isDone ? "" : String(nextOffset),
};
},
);
return {
async *[Symbol.asyncIterator]() {
for (const row of rows) yield row;
},
collect: vi.fn(async () => rows),
take: vi.fn(async (limit: number) => rows.slice(0, limit)),
paginate,
order: vi.fn(() => ({
collect: vi.fn(async () => rows),
take: vi.fn(async (limit: number) => rows.slice(0, limit)),
paginate: vi.fn(async ({ cursor, numItems }: { cursor: string | null; numItems: number }) => {
const offset = cursor ? Number(cursor) : 0;
const page = rows.slice(offset, offset + numItems);
const nextOffset = offset + page.length;
const isDone = nextOffset >= rows.length;
return {
page,
isDone,
continueCursor: isDone ? "" : String(nextOffset),
};
}),
paginate,
})),
};
}
@@ -2787,6 +2814,284 @@ describe("publishers membership controls", () => {
]);
});
it("computes OG metadata stats when publisher denormalized stats are missing", async () => {
const publisher = {
_id: "publishers:openclaw",
_creationTime: 1,
kind: "org",
handle: "openclaw",
displayName: "OpenClaw",
createdAt: 1,
updatedAt: 1,
};
const skill = {
_id: "skills:demo",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
moderationStatus: "active",
stats: { downloads: 42, stars: 2, installsCurrent: 4, installsAllTime: 7 },
};
const pkg = {
_id: "packages:demo",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
stats: { downloads: 8, installs: 5, stars: 1 },
};
const ctx = {
db: {
query: vi.fn((table: string) => ({
withIndex: vi.fn((indexName: string, buildQuery: (q: unknown) => unknown) => {
const fields: Record<string, unknown> = {};
const q = {
eq: (field: string, value: unknown) => {
fields[field] = value;
return q;
},
};
buildQuery(q);
if (table === "publishers" && indexName === "by_handle") {
return {
unique: vi.fn(async () => (fields.handle === "openclaw" ? publisher : null)),
};
}
if (table === "officialPublishers" && indexName === "by_publisher") {
return { unique: vi.fn(async () => null) };
}
if (table === "skills" && indexName === "by_owner_publisher_active_updated") {
return indexedRows(fields.ownerPublisherId === publisher._id ? [skill] : []);
}
if (table === "packages" && indexName === "by_owner_publisher_active_updated") {
return indexedRows(fields.ownerPublisherId === publisher._id ? [pkg] : []);
}
throw new Error(`unexpected ${table} index ${indexName}`);
}),
})),
},
};
const result = await getOgMetaByHandleHandler(ctx as never, { handle: "openclaw" });
expect(result?.stats).toEqual({
skills: 1,
packages: 1,
installs: 12,
downloads: 50,
stars: 3,
});
});
it("returns verified visible organization affiliations for user OG metadata", async () => {
const personalPublisher = {
_id: "publishers:teoslayer",
_creationTime: 1,
kind: "user",
userId: "users:teoslayer",
linkedUserId: "users:teoslayer",
handle: "teoslayer",
displayName: null,
createdAt: 1,
updatedAt: 1,
publishedSkills: 0,
publishedPackages: 0,
totalInstalls: 3,
totalDownloads: 10,
totalStars: 4,
statsDownloads: 10,
statsInstallsCurrent: 2,
statsInstallsAllTime: 3,
statsStars: 4,
stats: { downloads: 10, installsCurrent: 2, installsAllTime: 3, stars: 4 },
};
const user = {
_id: "users:teoslayer",
handle: "teoslayer",
displayName: "Calin Teodor",
image: "https://example.com/avatar.png",
bio: "Publisher @teoslayer on ClawHub.",
};
const openclawPublisher = {
_id: "publishers:openclaw",
_creationTime: 2,
kind: "org",
handle: "openclaw",
displayName: "OpenClaw",
image: "https://example.com/openclaw.png",
createdAt: 1,
updatedAt: 1,
};
const deactivatedPublisher = {
_id: "publishers:archived",
_creationTime: 3,
kind: "org",
handle: "archived",
displayName: "Archived",
image: "https://example.com/archived.png",
createdAt: 1,
updatedAt: 1,
deactivatedAt: 2,
};
const irrelevantMemberships = Array.from({ length: 70 }, (_, index) => ({
_id: `publisherMembers:missing-${index}`,
publisherId: `publishers:missing-${index}`,
userId: user._id,
role: "publisher",
}));
const memberships = [
{
_id: "publisherMembers:self",
publisherId: personalPublisher._id,
userId: user._id,
role: "owner",
},
...irrelevantMemberships,
{
_id: "publisherMembers:openclaw",
publisherId: openclawPublisher._id,
userId: user._id,
role: "publisher",
},
{
_id: "publisherMembers:archived",
publisherId: deactivatedPublisher._id,
userId: user._id,
role: "publisher",
},
];
const publishersById = new Map<string, unknown>([
[personalPublisher._id, personalPublisher],
[openclawPublisher._id, openclawPublisher],
[deactivatedPublisher._id, deactivatedPublisher],
]);
const usersById = new Map<string, unknown>([[user._id, user]]);
const ctx = {
db: {
get: vi.fn(async (id: string) => publishersById.get(id) ?? usersById.get(id) ?? null),
query: vi.fn((table: string) => ({
withIndex: vi.fn((indexName: string, buildQuery: (q: unknown) => unknown) => {
const fields: Record<string, unknown> = {};
const q = {
eq: (field: string, value: unknown) => {
fields[field] = value;
return q;
},
};
buildQuery(q);
if (table === "publishers" && indexName === "by_handle") {
return {
unique: vi.fn(async () =>
fields.handle === personalPublisher.handle ? personalPublisher : null,
),
};
}
if (table === "officialPublishers" && indexName === "by_publisher") {
return { unique: vi.fn(async () => null) };
}
if (table === "publisherMembers" && indexName === "by_user") {
return indexedRows(fields.userId === user._id ? memberships : []);
}
throw new Error(`unexpected ${table} index ${indexName}`);
}),
})),
},
};
const result = await getOgMetaByHandleHandler(ctx as never, { handle: "teoslayer" });
expect(result?.displayName).toBe("Calin Teodor");
expect(result?.affiliations).toEqual([
{
publisher: expect.objectContaining({
handle: "openclaw",
displayName: "OpenClaw",
image: "https://example.com/openclaw.png",
}),
role: "publisher",
},
]);
});
it("bounds raw membership rows scanned for user OG metadata", async () => {
const personalPublisher = {
_id: "publishers:teoslayer",
_creationTime: 1,
kind: "user",
linkedUserId: "users:teoslayer",
handle: "teoslayer",
displayName: null,
createdAt: 1,
updatedAt: 1,
publishedSkills: 0,
publishedPackages: 0,
totalInstalls: 3,
totalDownloads: 10,
totalStars: 4,
statsDownloads: 10,
statsInstallsCurrent: 2,
statsInstallsAllTime: 3,
statsStars: 4,
stats: { downloads: 10, installsCurrent: 2, installsAllTime: 3, stars: 4 },
};
const user = {
_id: "users:teoslayer",
handle: "teoslayer",
displayName: "Calin Teodor",
image: "https://example.com/avatar.png",
bio: "Publisher @teoslayer on ClawHub.",
};
const memberships = [
{
_id: "publisherMembers:self",
publisherId: personalPublisher._id,
userId: user._id,
role: "owner",
},
...Array.from({ length: 600 }, (_, index) => ({
_id: `publisherMembers:missing-${index}`,
publisherId: `publishers:missing-${index}`,
userId: user._id,
role: "publisher",
})),
];
const usersById = new Map<string, unknown>([[user._id, user]]);
const ctx = {
db: {
get: vi.fn(async (id: string) => usersById.get(id) ?? null),
query: vi.fn((table: string) => ({
withIndex: vi.fn((indexName: string, buildQuery: (q: unknown) => unknown) => {
const fields: Record<string, unknown> = {};
const q = {
eq: (field: string, value: unknown) => {
fields[field] = value;
return q;
},
};
buildQuery(q);
if (table === "publishers" && indexName === "by_handle") {
return {
unique: vi.fn(async () =>
fields.handle === personalPublisher.handle ? personalPublisher : null,
),
};
}
if (table === "officialPublishers" && indexName === "by_publisher") {
return { unique: vi.fn(async () => null) };
}
if (table === "publisherMembers" && indexName === "by_user") {
return indexedRows(fields.userId === user._id ? memberships : []);
}
throw new Error(`unexpected ${table} index ${indexName}`);
}),
})),
},
};
const result = await getOgMetaByHandleHandler(ctx as never, { handle: "teoslayer" });
expect(result?.affiliations).toEqual([]);
expect(ctx.db.get).toHaveBeenCalledWith("publishers:missing-510");
expect(ctx.db.get).not.toHaveBeenCalledWith("publishers:missing-511");
});
it("excludes hidden and removed skills from publisher catalogs", async () => {
const publisher = {
_id: "publishers:nvidia",
+87
View File
@@ -57,6 +57,9 @@ const PUBLISHER_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
const PUBLISHER_IMAGE_CONTENT_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);
const PUBLISHER_INVITE_TTL_MS = 7 * 24 * 60 * 60_000;
const MAX_PENDING_PUBLISHER_INVITES = 100;
const PUBLISHER_OG_AFFILIATION_LIMIT = 5;
const PUBLISHER_OG_MEMBERSHIP_PAGE_SIZE = 64;
const PUBLISHER_OG_MEMBERSHIP_SCAN_LIMIT = 512;
const publisherRoleValidator = v.union(
v.literal("owner"),
v.literal("admin"),
@@ -797,6 +800,61 @@ async function getUserPublisherAffiliations(
);
}
async function getUserPublisherOgAffiliations(
ctx: Pick<QueryCtx, "db">,
userId: Id<"users">,
currentPublisherId: Id<"publishers">,
) {
const affiliations: Array<{
publisher: NonNullable<ReturnType<typeof toPublicPublisher>>;
role: Doc<"publisherMembers">["role"];
}> = [];
let cursor: string | null = null;
let scannedMemberships = 0;
while (
affiliations.length < PUBLISHER_OG_AFFILIATION_LIMIT &&
scannedMemberships < PUBLISHER_OG_MEMBERSHIP_SCAN_LIMIT
) {
const page = await ctx.db
.query("publisherMembers")
.withIndex("by_user", (q) => q.eq("userId", userId))
.paginate({
cursor,
numItems: Math.min(
PUBLISHER_OG_MEMBERSHIP_PAGE_SIZE,
PUBLISHER_OG_MEMBERSHIP_SCAN_LIMIT - scannedMemberships,
),
});
scannedMemberships += page.page.length;
for (const membership of page.page) {
if (affiliations.length >= PUBLISHER_OG_AFFILIATION_LIMIT) break;
if (membership.publisherId === currentPublisherId) continue;
const publisher = await ctx.db.get(membership.publisherId);
if (
!publisher ||
publisher.kind !== "org" ||
publisher.deletedAt ||
publisher.deactivatedAt
) {
continue;
}
const publicPublisher = await toPublicPublisherWithOfficial(ctx, publisher);
if (!publicPublisher) continue;
affiliations.push({
publisher: publicPublisher,
role: membership.role,
});
}
if (page.isDone || page.page.length === 0) break;
cursor = page.continueCursor;
}
return affiliations;
}
async function toPublicPublisherWithLinkedImage(
ctx: Pick<QueryCtx, "db">,
publisher: Doc<"publishers"> | null,
@@ -2199,6 +2257,35 @@ export const getProfileByHandle = query({
},
});
export const getOgMetaByHandle = query({
args: { handle: v.string() },
handler: async (ctx, args) => {
const visibility = await getPublicPublisherVisibility(
ctx,
await getPublisherByHandle(ctx, args.handle),
);
if (!visibility) return null;
const publicPublisher = await toPublicPublisherWithOfficial(ctx, visibility.publisher);
if (!publicPublisher) return null;
const visibleUserId = visibility.publisher.kind === "user" ? visibility.linkedUser?._id : null;
const stats = hasPublisherStats(visibility.publisher)
? getPublisherDenormalizedStats(visibility.publisher)
: getIndexedPublisherStatsFromRows(
await getPublisherPublishedRows(ctx, visibility.publisher._id),
);
return {
...publicPublisher,
displayName: resolvePublisherDisplayName(visibility.publisher, visibility.linkedUser),
image: publicPublisher.image ?? visibility.linkedUser?.image,
bio: publicPublisher.bio ?? visibility.linkedUser?.bio,
stats,
affiliations: visibleUserId
? await getUserPublisherOgAffiliations(ctx, visibleUserId, visibility.publisher._id)
: [],
};
},
});
export const listStarredPage = query({
args: {
handle: v.string(),
+2 -2
View File
@@ -16,7 +16,7 @@ vi.mock("convex/browser", () => ({
}));
vi.mock("../../convex/_generated/api", () => ({
api: { publishers: { getProfileByHandle: "publishers.getProfileByHandle" } },
api: { publishers: { getOgMetaByHandle: "publishers.getOgMetaByHandle" } },
}));
describe("fetchPublisherOgMeta", () => {
@@ -53,7 +53,7 @@ describe("fetchPublisherOgMeta", () => {
const meta = await fetchPublisherOgMeta("openclaw", "https://example.convex.cloud");
expect(clientCtorMock).toHaveBeenCalledWith("https://example.convex.cloud");
expect(queryMock).toHaveBeenCalledWith("publishers.getProfileByHandle", {
expect(queryMock).toHaveBeenCalledWith("publishers.getOgMetaByHandle", {
handle: "openclaw",
});
expect(meta?.stats.downloads).toBe(99);
+4 -22
View File
@@ -1,4 +1,5 @@
import { ConvexHttpClient } from "convex/browser";
import type { FunctionReturnType } from "convex/server";
import { api } from "../../convex/_generated/api";
export type PublisherOgMeta = {
@@ -18,26 +19,7 @@ export type PublisherOgMeta = {
};
};
type PublisherProfileResult = {
handle?: string | null;
kind?: "user" | "org";
official?: boolean;
displayName?: string | null;
bio?: string | null;
image?: string | null;
affiliations?: Array<{
publisher?: {
handle?: string | null;
displayName?: string | null;
image?: string | null;
} | null;
}>;
stats?: {
downloads?: number;
installs?: number;
};
} | null;
type PublisherProfileResult = FunctionReturnType<typeof api.publishers.getOgMetaByHandle>;
type PublisherProfileAffiliations = NonNullable<PublisherProfileResult>["affiliations"];
export async function fetchPublisherOgMeta(
@@ -46,9 +28,9 @@ export async function fetchPublisherOgMeta(
): Promise<PublisherOgMeta | null> {
try {
const client = new ConvexHttpClient(convexUrl);
const profile = (await client.query(api.publishers.getProfileByHandle, {
const profile = await client.query(api.publishers.getOgMetaByHandle, {
handle,
})) as PublisherProfileResult;
});
if (!profile) return null;
return {
handle: profile.handle ?? null,
+22
View File
@@ -59,4 +59,26 @@ describe("normalizeOgLogoDataUrl", () => {
await expect(visibleAlphaBounds(paddedLogo ?? "")).resolves.toEqual({ width: 48, height: 48 });
await expect(visibleAlphaBounds(tightLogo ?? "")).resolves.toEqual({ width: 48, height: 48 });
});
it("fails closed for non-image and invalid image data URLs", async () => {
await expect(normalizeOgLogoDataUrl("data:text/plain;base64,SGVsbG8=")).resolves.toBeNull();
await expect(normalizeOgLogoDataUrl("data:image/png;base64,bm90LWEtcG5n")).resolves.toBeNull();
});
it("fails closed for images above the input pixel cap", async () => {
const oversizedImage = await sharp({
create: {
width: 2001,
height: 2001,
channels: 4,
background: { r: 212, g: 69, b: 58, alpha: 1 },
},
})
.png()
.toBuffer();
await expect(
normalizeOgLogoDataUrl(`data:image/png;base64,${oversizedImage.toString("base64")}`),
).resolves.toBeNull();
});
});
+6 -3
View File
@@ -1,6 +1,7 @@
import sharp from "sharp";
const NORMALIZED_LOGO_SIZE = 48;
const NORMALIZED_LOGO_MAX_INPUT_PIXELS = 4_000_000;
function readDataUrl(dataUrl: string) {
const match = /^data:([^;,]+);base64,(.+)$/s.exec(dataUrl);
@@ -14,10 +15,12 @@ function readDataUrl(dataUrl: string) {
export async function normalizeOgLogoDataUrl(dataUrl: string | null | undefined) {
if (!dataUrl) return null;
const parsed = readDataUrl(dataUrl);
if (!parsed || !parsed.mimeType.startsWith("image/")) return dataUrl;
if (!parsed || !parsed.mimeType.startsWith("image/")) return null;
try {
const normalized = await sharp(parsed.buffer)
const normalized = await sharp(parsed.buffer, {
limitInputPixels: NORMALIZED_LOGO_MAX_INPUT_PIXELS,
})
.ensureAlpha()
.trim({ threshold: 8 })
.resize(NORMALIZED_LOGO_SIZE, NORMALIZED_LOGO_SIZE, {
@@ -29,6 +32,6 @@ export async function normalizeOgLogoDataUrl(dataUrl: string | null | undefined)
.toBuffer();
return `data:image/png;base64,${normalized.toString("base64")}`;
} catch {
return dataUrl;
return null;
}
}
@@ -36,11 +36,11 @@ vi.mock("h3", () => ({
setHeader: (...args: unknown[]) => setHeaderMock(...args),
}));
vi.mock("../../og/fetchPluginOgMeta", () => ({
vi.mock("./fetchPluginOgMeta", () => ({
fetchPluginOgMeta: (...args: unknown[]) => fetchPluginOgMetaMock(...args),
}));
vi.mock("../../og/ogAssets", () => ({
vi.mock("./ogAssets", () => ({
FONT_MONO: "IBM Plex Mono",
FONT_SANS: "Bricolage Grotesque",
getMarkDataUrl: (...args: unknown[]) => getMarkDataUrlMock(...args),
@@ -49,11 +49,11 @@ vi.mock("../../og/ogAssets", () => ({
getFontBuffers: (...args: unknown[]) => getFontBuffersMock(...args),
}));
vi.mock("../../og/fetchImageDataUrl", () => ({
vi.mock("./fetchImageDataUrl", () => ({
fetchImageDataUrl: vi.fn(async () => null),
}));
vi.mock("../../og/pluginOgSvg", () => ({
vi.mock("./pluginOgSvg", () => ({
buildPluginOgSvg: (...args: unknown[]) => buildPluginOgSvgMock(...args),
}));
@@ -93,7 +93,7 @@ describe("plugin og route", () => {
it("returns plain text when name is missing", async () => {
getQueryMock.mockReturnValue({});
const handler = (await import("./plugin.png")).default;
const handler = (await import("../routes/og/plugin.png")).default;
await expect(handler({} as never)).resolves.toBe("Missing `name` query param.");
expect(setHeaderMock).toHaveBeenCalledWith({}, "Content-Type", "text/plain; charset=utf-8");
@@ -115,7 +115,7 @@ describe("plugin og route", () => {
verification: { scanStatus: "pending" },
});
const handler = (await import("./plugin.png")).default;
const handler = (await import("../routes/og/plugin.png")).default;
const response = (await handler({} as never)) as Response;
expect(fetchPluginOgMetaMock).toHaveBeenCalledWith(
@@ -146,7 +146,7 @@ describe("plugin og route", () => {
verification: { scanStatus: "clean" },
});
const handler = (await import("./plugin.png")).default;
const handler = (await import("../routes/og/plugin.png")).default;
const response = (await handler({} as never)) as Response;
expect(response.headers.get("Cache-Control")).toBe("public, max-age=31536000, immutable");
@@ -169,7 +169,7 @@ describe("plugin og route", () => {
downloads: "0",
});
const handler = (await import("./plugin.png")).default;
const handler = (await import("../routes/og/plugin.png")).default;
await handler({} as never);
expect(fetchPluginOgMetaMock).not.toHaveBeenCalled();
@@ -193,7 +193,7 @@ describe("plugin og route", () => {
installs: "9.9k",
});
const handler = (await import("./plugin.png")).default;
const handler = (await import("../routes/og/plugin.png")).default;
await handler({} as never);
expect(fetchPluginOgMetaMock).not.toHaveBeenCalled();
+394
View File
@@ -0,0 +1,394 @@
/* @vitest-environment node */
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const getQueryMock = vi.fn();
const setHeaderMock = vi.fn();
const fetchImageDataUrlMock = vi.fn();
const fetchPublisherProfileImageDataUrlMock = vi.fn();
const fetchPublisherOgMetaMock = vi.fn();
const normalizeOgLogoDataUrlMock = vi.fn();
const getClawHubLogoDataUrlMock = vi.fn();
const ensureResvgWasmMock = vi.fn();
const getPublisherFontBuffersMock = vi.fn();
const buildPublisherOgSvgMock = vi.fn();
const renderAsPngMock = vi.fn();
const freeMock = vi.fn();
const resvgCtorMock = vi.fn();
class ResvgMockClass {
constructor(...args: unknown[]) {
resvgCtorMock(...args);
}
render() {
return { asPng: renderAsPngMock };
}
free() {
return freeMock();
}
}
vi.mock("h3", () => ({
defineEventHandler: (handler: unknown) => handler,
getQuery: (...args: unknown[]) => getQueryMock(...args),
setHeader: (...args: unknown[]) => setHeaderMock(...args),
}));
vi.mock("./fetchImageDataUrl", () => ({
fetchImageDataUrl: (...args: unknown[]) => fetchImageDataUrlMock(...args),
fetchPublisherProfileImageDataUrl: (...args: unknown[]) =>
fetchPublisherProfileImageDataUrlMock(...args),
}));
vi.mock("./fetchPublisherOgMeta", () => ({
fetchPublisherOgMeta: (...args: unknown[]) => fetchPublisherOgMetaMock(...args),
}));
vi.mock("./normalizeLogoDataUrl", () => ({
normalizeOgLogoDataUrl: (...args: unknown[]) => normalizeOgLogoDataUrlMock(...args),
}));
vi.mock("./ogAssets", () => ({
FONT_MONO: "IBM Plex Mono",
FONT_SANS: "Bricolage Grotesque",
getClawHubLogoDataUrl: (...args: unknown[]) => getClawHubLogoDataUrlMock(...args),
ensureResvgWasm: (...args: unknown[]) => ensureResvgWasmMock(...args),
getPublisherFontBuffers: (...args: unknown[]) => getPublisherFontBuffersMock(...args),
}));
vi.mock("./publisherOgSvg", () => ({
buildPublisherOgSvg: (...args: unknown[]) => buildPublisherOgSvgMock(...args),
}));
vi.mock("@resvg/resvg-wasm", () => ({
Resvg: ResvgMockClass,
}));
beforeEach(() => {
vi.resetModules();
getQueryMock.mockReset();
setHeaderMock.mockReset();
fetchImageDataUrlMock.mockReset();
fetchPublisherProfileImageDataUrlMock.mockReset();
fetchPublisherOgMetaMock.mockReset();
normalizeOgLogoDataUrlMock.mockReset();
getClawHubLogoDataUrlMock.mockReset();
ensureResvgWasmMock.mockReset();
getPublisherFontBuffersMock.mockReset();
buildPublisherOgSvgMock.mockReset();
renderAsPngMock.mockReset();
freeMock.mockReset();
resvgCtorMock.mockReset();
getClawHubLogoDataUrlMock.mockResolvedValue("data:image/png;base64,TE9HTw==");
ensureResvgWasmMock.mockResolvedValue(undefined);
getPublisherFontBuffersMock.mockResolvedValue([new Uint8Array([1, 2, 3])]);
fetchPublisherProfileImageDataUrlMock.mockResolvedValue("data:image/png;base64,QVZBVEFS");
fetchImageDataUrlMock.mockImplementation(async (url: string) => `data:image/png;base64,${url}`);
normalizeOgLogoDataUrlMock.mockImplementation(async (dataUrl: string) => `${dataUrl}-normalized`);
buildPublisherOgSvgMock.mockReturnValue("<svg>profile</svg>");
renderAsPngMock.mockReturnValue(new Uint8Array([7, 8, 9]));
});
afterEach(() => {
delete process.env.VITE_CONVEX_URL;
delete process.env.CONVEX_URL;
});
describe("profile og route", () => {
it("returns plain text when handle is missing", async () => {
getQueryMock.mockReturnValue({});
const handler = (await import("../routes/og/profile.png")).default;
await expect(handler({} as never)).resolves.toBe("Missing `handle` query param.");
expect(setHeaderMock).toHaveBeenCalledWith({}, "Content-Type", "text/plain; charset=utf-8");
expect(fetchPublisherOgMetaMock).not.toHaveBeenCalled();
expect(resvgCtorMock).not.toHaveBeenCalled();
});
it("renders explicit query params without fetching metadata", async () => {
getQueryMock.mockReturnValue({
handle: "nvidia",
title: "NVIDIA",
downloads: "1200",
kind: "org",
official: "0",
orgState: "0",
orgImages: "0",
avatar: "https://cdn.example.com/avatar.png",
});
const handler = (await import("../routes/og/profile.png")).default;
const response = (await handler({} as never)) as Response;
await expect(response.arrayBuffer()).resolves.toEqual(new Uint8Array([7, 8, 9]).buffer);
expect(response.headers.get("Content-Type")).toBe("image/png");
expect(fetchPublisherOgMetaMock).not.toHaveBeenCalled();
expect(fetchPublisherProfileImageDataUrlMock).toHaveBeenCalledWith(
"https://cdn.example.com/avatar.png",
);
expect(fetchImageDataUrlMock).not.toHaveBeenCalled();
expect(normalizeOgLogoDataUrlMock).not.toHaveBeenCalled();
expect(buildPublisherOgSvgMock).toHaveBeenCalledWith({
clawHubLogoDataUrl: "data:image/png;base64,TE9HTw==",
avatarDataUrl: "data:image/png;base64,QVZBVEFS",
avatarShape: "rounded",
official: false,
title: "NVIDIA",
handleLabel: "@nvidia",
organizationCount: 0,
organizationLogos: [],
stats: [{ value: "1.2k", label: "Downloads" }],
});
});
it("verifies trust indicators before rendering query-requested official state", async () => {
process.env.VITE_CONVEX_URL = "https://convex.example";
getQueryMock.mockReturnValue({
handle: "nvidia",
title: "Fake NVIDIA",
downloads: "999999",
official: "1",
orgState: "many",
orgImages: "https://attacker.example/0.png|https://attacker.example/1.png",
avatar: "https://attacker.example/avatar.png",
});
fetchPublisherOgMetaMock.mockResolvedValue({
handle: "nvidia",
kind: "user",
official: false,
displayName: "Verified NVIDIA",
image: "https://cdn.example.com/verified-avatar.png",
affiliations: [],
stats: { downloads: 1200 },
});
const handler = (await import("../routes/og/profile.png")).default;
await handler({} as never);
expect(fetchPublisherOgMetaMock).toHaveBeenCalledWith("nvidia", "https://convex.example");
expect(fetchPublisherProfileImageDataUrlMock).toHaveBeenCalledWith(
"https://cdn.example.com/verified-avatar.png",
);
expect(fetchImageDataUrlMock).not.toHaveBeenCalled();
expect(normalizeOgLogoDataUrlMock).not.toHaveBeenCalled();
expect(buildPublisherOgSvgMock).toHaveBeenCalledWith(
expect.objectContaining({
official: false,
title: "Verified NVIDIA",
organizationCount: 0,
organizationLogos: [],
stats: [{ value: "1.2k", label: "Downloads" }],
}),
);
});
it("verifies non-zero organization state even when affiliations have no images", async () => {
process.env.VITE_CONVEX_URL = "https://convex.example";
getQueryMock.mockReturnValue({
handle: "nvidia",
title: "Fake NVIDIA",
downloads: "999999",
kind: "user",
official: "0",
orgState: "many",
orgImages: "0",
avatar: "https://attacker.example/avatar.png",
});
fetchPublisherOgMetaMock.mockResolvedValue({
handle: "nvidia",
kind: "user",
official: false,
displayName: "Verified NVIDIA",
image: "https://cdn.example.com/verified-avatar.png",
affiliations: [
{
handle: "verified-org-1",
displayName: "Verified Org 1",
image: null,
},
{
handle: "verified-org-2",
displayName: "Verified Org 2",
image: null,
},
],
stats: { downloads: 1200 },
});
const handler = (await import("../routes/og/profile.png")).default;
await handler({} as never);
expect(fetchPublisherOgMetaMock).toHaveBeenCalledWith("nvidia", "https://convex.example");
expect(fetchPublisherProfileImageDataUrlMock).toHaveBeenCalledWith(
"https://cdn.example.com/verified-avatar.png",
);
expect(fetchImageDataUrlMock).not.toHaveBeenCalled();
expect(normalizeOgLogoDataUrlMock).not.toHaveBeenCalled();
expect(buildPublisherOgSvgMock).toHaveBeenCalledWith(
expect.objectContaining({
official: false,
title: "Verified NVIDIA",
organizationCount: 2,
organizationLogos: [],
stats: [{ value: "1.2k", label: "Downloads" }],
}),
);
});
it("uses verified metadata for official cards requested by query params", async () => {
process.env.VITE_CONVEX_URL = "https://convex.example";
getQueryMock.mockReturnValue({
handle: "nvidia",
title: "Fake NVIDIA",
downloads: "999999",
kind: "user",
official: "1",
orgImages: "https://attacker.example/org.png",
avatar: "https://attacker.example/avatar.png",
});
fetchPublisherOgMetaMock.mockResolvedValue({
handle: "nvidia",
kind: "org",
official: true,
displayName: "Verified NVIDIA",
image: "https://cdn.example.com/verified-avatar.png",
affiliations: [
{
handle: "verified-org",
displayName: "Verified Org",
image: "https://cdn.example.com/verified-org.png",
},
],
stats: { downloads: 1200 },
});
const handler = (await import("../routes/og/profile.png")).default;
await handler({} as never);
expect(fetchPublisherOgMetaMock).toHaveBeenCalledWith("nvidia", "https://convex.example");
expect(fetchPublisherProfileImageDataUrlMock).toHaveBeenCalledWith(
"https://cdn.example.com/verified-avatar.png",
);
expect(fetchImageDataUrlMock).toHaveBeenCalledTimes(1);
expect(fetchImageDataUrlMock).toHaveBeenCalledWith("https://cdn.example.com/verified-org.png", {
allowPublicHttps: true,
followRedirects: true,
});
expect(buildPublisherOgSvgMock).toHaveBeenCalledWith(
expect.objectContaining({
avatarShape: "rounded",
official: true,
title: "Verified NVIDIA",
organizationCount: 1,
organizationLogos: [
"data:image/png;base64,https://cdn.example.com/verified-org.png-normalized",
],
stats: [{ value: "1.2k", label: "Downloads" }],
}),
);
});
it("uses verified metadata when fetched metadata adds trust indicators", async () => {
process.env.VITE_CONVEX_URL = "https://convex.example";
getQueryMock.mockReturnValue({
handle: "nvidia",
title: "Fake NVIDIA",
downloads: "999999",
kind: "user",
official: "0",
orgImages: "0",
});
fetchPublisherOgMetaMock.mockResolvedValue({
handle: "nvidia",
kind: "org",
official: true,
displayName: "Verified NVIDIA",
image: "https://cdn.example.com/verified-avatar.png",
affiliations: [
{
handle: "verified-org",
displayName: "Verified Org",
image: "https://cdn.example.com/verified-org.png",
},
],
stats: { downloads: 1200 },
});
const handler = (await import("../routes/og/profile.png")).default;
await handler({} as never);
expect(fetchPublisherOgMetaMock).toHaveBeenCalledWith("nvidia", "https://convex.example");
expect(fetchPublisherProfileImageDataUrlMock).toHaveBeenCalledWith(
"https://cdn.example.com/verified-avatar.png",
);
expect(fetchImageDataUrlMock).toHaveBeenCalledWith("https://cdn.example.com/verified-org.png", {
allowPublicHttps: true,
followRedirects: true,
});
expect(buildPublisherOgSvgMock).toHaveBeenCalledWith(
expect.objectContaining({
avatarShape: "rounded",
official: true,
title: "Verified NVIDIA",
organizationCount: 1,
organizationLogos: [
"data:image/png;base64,https://cdn.example.com/verified-org.png-normalized",
],
stats: [{ value: "1.2k", label: "Downloads" }],
}),
);
});
it("caps metadata affiliation logos before fetching and normalizing", async () => {
process.env.VITE_CONVEX_URL = "https://convex.example";
getQueryMock.mockReturnValue({ handle: "nvidia" });
fetchPublisherOgMetaMock.mockResolvedValue({
handle: "nvidia",
kind: "user",
official: true,
displayName: "NVIDIA",
image: "https://cdn.example.com/avatar.png",
affiliations: Array.from({ length: 7 }, (_, index) => ({
handle: `org-${index}`,
displayName: `Org ${index}`,
image: `https://cdn.example.com/org-${index}.png`,
})),
stats: { downloads: 1200 },
});
normalizeOgLogoDataUrlMock.mockImplementation(async (dataUrl: string) =>
dataUrl.includes("org-2") ? null : `${dataUrl}-normalized`,
);
const handler = (await import("../routes/og/profile.png")).default;
await handler({} as never);
expect(fetchPublisherOgMetaMock).toHaveBeenCalledWith("nvidia", "https://convex.example");
expect(fetchImageDataUrlMock).toHaveBeenCalledTimes(5);
expect(fetchImageDataUrlMock.mock.calls.map((call) => call[0])).toEqual([
"https://cdn.example.com/org-0.png",
"https://cdn.example.com/org-1.png",
"https://cdn.example.com/org-2.png",
"https://cdn.example.com/org-3.png",
"https://cdn.example.com/org-4.png",
]);
expect(normalizeOgLogoDataUrlMock).toHaveBeenCalledTimes(5);
expect(buildPublisherOgSvgMock).toHaveBeenCalledWith(
expect.objectContaining({
official: true,
title: "NVIDIA",
handleLabel: "@nvidia",
organizationCount: 5,
organizationLogos: [
"data:image/png;base64,https://cdn.example.com/org-0.png-normalized",
"data:image/png;base64,https://cdn.example.com/org-1.png-normalized",
"data:image/png;base64,https://cdn.example.com/org-3.png-normalized",
"data:image/png;base64,https://cdn.example.com/org-4.png-normalized",
],
}),
);
});
});
+29
View File
@@ -144,8 +144,23 @@ describe("buildPublisherOgSvg", () => {
expect(svg).not.toContain('rx="8" fill="#F7F1EA"');
});
it("renders fallback organization tiles when verified affiliations have no logos", () => {
const svg = buildSvg({
organizationCount: 2,
organizationLogos: [],
});
expect(svg).toContain("Organizations");
expect(svg).toContain("orgLogoClip0");
expect(svg).toContain("orgLogoClip1");
expect(svg).not.toContain("orgLogoClip2");
expect(svg).toContain(
`<image href="${clawHubLogoDataUrl}" x="169" y="459" width="48" height="48" clip-path="url(#orgLogoClip0)"`,
);
});
it("caps rendered organization logos at five", () => {
const svg = buildSvg({
organizationCount: 6,
organizationLogos: [
transparentPixel,
transparentPixel,
@@ -187,4 +202,18 @@ describe("buildPublisherOgSvg", () => {
expect(svg).toContain('x="447" y="547"');
expect(svg).toContain(">41.9k</text>");
});
it("accounts for full-width glyphs when fitting publisher text", () => {
const svg = buildSvg({
official: true,
title: "这是一个非常长的发布者名称测试测试测试测试测试",
handleLabel: "@测试测试测试测试测试测试测试测试测试测试",
});
expect(svg).toContain('<tspan x="447" dy="0">这是一个非常长的</tspan>');
expect(svg).toContain('<tspan x="447" dy="84">发布者名称测试...</tspan>');
expect(svg).toContain("@测试测试测试测试测试测试测...");
expect(readOfficialBadgeX(svg)).toBe(1037.7);
expect(svg).not.toContain("发布者名称测试测试测试测试测试</tspan>");
});
});
+45 -15
View File
@@ -8,6 +8,7 @@ export type PublisherOgSvgParams = {
official?: boolean;
title: string;
handleLabel: string;
organizationCount?: number;
organizationLogos?: string[];
stats?: RegistryOgStat[];
};
@@ -28,14 +29,27 @@ const PUBLISHER_GRADIENT_FADE = "#6C1B2B";
const PUBLISHER_TEXT_WEIGHT = 700;
const PUBLISHER_LABEL_SIZE = 24;
const PUBLISHER_VALUE_SIZE = 44;
const GRAPHEME_SEGMENTER =
typeof Intl.Segmenter === "function"
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
: null;
const FULL_WIDTH_GLYPH_RE =
/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\u3000-\u303f\uff00-\uffef]/u;
function textSegments(value: string) {
return GRAPHEME_SEGMENTER
? Array.from(GRAPHEME_SEGMENTER.segment(value), (part) => part.segment)
: Array.from(value);
}
function estimateTextWidth(value: string, fontSize: number) {
return [...value].reduce((width, char) => {
return textSegments(value).reduce((width, char) => {
if (char === " ") return width + fontSize * 0.28;
if (/[ilI.,:;|!'"`]/.test(char)) return width + fontSize * 0.28;
if (/[mwMW@%&]/.test(char)) return width + fontSize * 0.9;
if (/[A-Z]/.test(char)) return width + fontSize * 0.68;
if (/[0-9]/.test(char)) return width + fontSize * 0.6;
if (FULL_WIDTH_GLYPH_RE.test(char)) return width + fontSize;
return width + fontSize * 0.56;
}, 0);
}
@@ -97,7 +111,7 @@ function wrapTextWithoutEllipsis(value: string, maxWidth: number, fontSize: numb
if (estimateTextWidth(word, fontSize) <= maxWidth) return [word];
const parts: string[] = [];
let chunk = "";
for (const char of word) {
for (const char of textSegments(word)) {
const next = chunk + char;
if (chunk && estimateTextWidth(next, fontSize) > maxWidth) {
parts.push(chunk);
@@ -181,7 +195,7 @@ function truncateWithDots(value: string, maxWidth: number, fontSize: number) {
if (estimateTextWidth(value, fontSize) <= maxWidth) return value;
const dots = "...";
const dotsWidth = estimateTextWidth(dots, fontSize);
const chars = [...value];
const chars = textSegments(value);
while (chars.length > 0 && estimateTextWidth(chars.join(""), fontSize) + dotsWidth > maxWidth) {
chars.pop();
}
@@ -217,24 +231,30 @@ function statColumn(
</g>`;
}
function orgLogoTiles(logos: string[], fallbackLogoDataUrl: string, x: number, yOffset: number) {
function orgLogoTiles(
logos: string[],
organizationCount: number,
fallbackLogoDataUrl: string,
x: number,
yOffset: number,
) {
const visibleLogos = logos.slice(0, 5);
if (visibleLogos.length === 0) return "";
const visibleCount = Math.min(Math.max(organizationCount, visibleLogos.length), 5);
if (visibleCount === 0) return "";
const y = 459 + yOffset;
const size = 48;
const gap = 10;
const tiles = visibleLogos
.map((logo, index) => {
const tileX = x + index * (size + gap);
const clipId = `orgLogoClip${index}`;
return `<g>
const tiles = Array.from({ length: visibleCount }, (_, index) => {
const logo = visibleLogos[index] || fallbackLogoDataUrl;
const tileX = x + index * (size + gap);
const clipId = `orgLogoClip${index}`;
return `<g>
<clipPath id="${clipId}">
<rect x="${tileX}" y="${y}" width="${size}" height="${size}" rx="8"/>
</clipPath>
<image href="${logo || fallbackLogoDataUrl}" x="${tileX}" y="${y}" width="${size}" height="${size}" clip-path="url(#${clipId})" preserveAspectRatio="xMidYMid slice"/>
<image href="${logo}" x="${tileX}" y="${y}" width="${size}" height="${size}" clip-path="url(#${clipId})" preserveAspectRatio="xMidYMid slice"/>
</g>`;
})
.join("");
}).join("");
return `<g>
<text x="${x}" y="${438 + yOffset}"
fill="${PUBLISHER_RED}"
@@ -250,7 +270,11 @@ export function buildPublisherOgSvg(params: PublisherOgSvgParams) {
const avatar = params.avatarDataUrl || params.clawHubLogoDataUrl;
const avatarShape = params.avatarShape ?? "circle";
const organizationLogos = params.organizationLogos?.filter(Boolean) ?? [];
const hasOrganizations = organizationLogos.length > 0;
const organizationCount = Math.min(
Math.max(params.organizationCount ?? 0, organizationLogos.length),
5,
);
const hasOrganizations = organizationCount > 0;
const normalLayout = hasOrganizations
? {
titleX: 509,
@@ -410,7 +434,13 @@ export function buildPublisherOgSvg(params: PublisherOgSvgParams) {
font-family="${FONT_SANS}, sans-serif">on ClawHub</text>
${statsMarkup}
${orgLogoTiles(organizationLogos, params.clawHubLogoDataUrl, orgLogosX, organizationExtraGap)}
${orgLogoTiles(
organizationLogos,
organizationCount,
params.clawHubLogoDataUrl,
orgLogosX,
organizationExtraGap,
)}
</g>
</svg>`;
}
@@ -36,11 +36,11 @@ vi.mock("h3", () => ({
setHeader: (...args: unknown[]) => setHeaderMock(...args),
}));
vi.mock("../../og/fetchSkillOgMeta", () => ({
vi.mock("./fetchSkillOgMeta", () => ({
fetchSkillOgMeta: (...args: unknown[]) => fetchSkillOgMetaMock(...args),
}));
vi.mock("../../og/ogAssets", () => ({
vi.mock("./ogAssets", () => ({
FONT_MONO: "IBM Plex Mono",
FONT_SANS: "Bricolage Grotesque",
getMarkDataUrl: (...args: unknown[]) => getMarkDataUrlMock(...args),
@@ -49,11 +49,11 @@ vi.mock("../../og/ogAssets", () => ({
getFontBuffers: (...args: unknown[]) => getFontBuffersMock(...args),
}));
vi.mock("../../og/fetchImageDataUrl", () => ({
vi.mock("./fetchImageDataUrl", () => ({
fetchImageDataUrl: vi.fn(async () => null),
}));
vi.mock("../../og/skillOgSvg", () => ({
vi.mock("./skillOgSvg", () => ({
buildSkillOgSvg: (...args: unknown[]) => buildSkillOgSvgMock(...args),
}));
@@ -93,7 +93,7 @@ describe("skill og route", () => {
it("returns plain text when slug is missing", async () => {
getQueryMock.mockReturnValue({});
const handler = (await import("./skill.png")).default;
const handler = (await import("../routes/og/skill.png")).default;
await expect(handler({} as never)).resolves.toBe("Missing `slug` query param.");
expect(setHeaderMock).toHaveBeenCalledWith({}, "Content-Type", "text/plain; charset=utf-8");
@@ -111,7 +111,7 @@ describe("skill og route", () => {
downloads: "0",
});
const handler = (await import("./skill.png")).default;
const handler = (await import("../routes/og/skill.png")).default;
const response = (await handler({} as never)) as Response;
await expect(response.arrayBuffer()).resolves.toEqual(new Uint8Array([7, 8, 9]).buffer);
expect(response.headers.get("Cache-Control")).toBe("public, max-age=31536000, immutable");
@@ -161,7 +161,7 @@ describe("skill og route", () => {
moderation: { verdict: "clean", isSuspicious: false, isMalwareBlocked: false },
});
const handler = (await import("./skill.png")).default;
const handler = (await import("../routes/og/skill.png")).default;
const response = (await handler({} as never)) as Response;
expect(fetchSkillOgMetaMock).toHaveBeenCalledWith(
@@ -195,7 +195,7 @@ describe("skill og route", () => {
installs: "9.9k",
});
const handler = (await import("./skill.png")).default;
const handler = (await import("../routes/og/skill.png")).default;
await handler({} as never);
expect(fetchSkillOgMetaMock).not.toHaveBeenCalled();
@@ -219,7 +219,7 @@ describe("skill og route", () => {
downloads: "43456",
});
const handler = (await import("./skill.png")).default;
const handler = (await import("../routes/og/skill.png")).default;
await handler({} as never);
expect(buildSkillOgSvgMock).toHaveBeenCalledWith(
+31 -11
View File
@@ -52,6 +52,11 @@ function readOrganizationImagesQuery(value: unknown) {
.slice(0, 5);
}
function readOrganizationStateQuery(value: unknown) {
const raw = cleanString(value).toLowerCase();
return raw !== "" && raw !== "0" && raw !== "false" && raw !== "none";
}
export default defineEventHandler(async (event) => {
const query = getQuery(event) as OgQuery;
const handle = cleanString(query.handle).replace(/^@+/, "");
@@ -65,26 +70,40 @@ export default defineEventHandler(async (event) => {
const avatarFromQuery = cleanString(query.avatar);
const organizationImagesFromQuery = readOrganizationImagesQuery(query.orgImages);
const convexUrl = getConvexUrl();
const requestsTrustedPublisherState =
readBooleanQuery(query.official) ||
readOrganizationStateQuery(query.orgState) ||
organizationImagesFromQuery.length > 0;
const needFetch =
!titleFromQuery ||
!readOgDownloadsQuery(query) ||
!avatarFromQuery ||
!cleanString(query.official) ||
!cleanString(query.orgState) ||
!cleanString(query.orgImages);
requestsTrustedPublisherState;
const meta = needFetch && convexUrl ? await fetchPublisherOgMeta(handle, convexUrl) : null;
const fetchedTrustedPublisherState =
Boolean(meta?.official) || (meta?.affiliations.length ?? 0) > 0;
const useVerifiedPublisherState =
meta !== null && (requestsTrustedPublisherState || fetchedTrustedPublisherState);
const verifiedMeta = useVerifiedPublisherState ? meta : null;
const handleLabel = `@${meta?.handle || handle}`;
const title = titleFromQuery || meta?.displayName || handleLabel;
const title = verifiedMeta
? verifiedMeta.displayName || handleLabel
: titleFromQuery || meta?.displayName || handleLabel;
const avatarUrl = verifiedMeta ? verifiedMeta.image : avatarFromQuery || meta?.image;
const avatarKind = verifiedMeta ? verifiedMeta.kind : kindFromQuery || meta?.kind;
const statsQuery = useVerifiedPublisherState ? {} : query;
const [clawHubLogoDataUrl, fontBuffers] = await Promise.all([
getClawHubLogoDataUrl(),
ensureResvgWasm().then(() => getPublisherFontBuffers()),
]);
const avatarDataUrl = await fetchPublisherProfileImageDataUrl(avatarFromQuery || meta?.image);
const avatarDataUrl = await fetchPublisherProfileImageDataUrl(avatarUrl);
const organizationImageUrls =
organizationImagesFromQuery.length > 0
? organizationImagesFromQuery
: (meta?.affiliations.map((affiliation) => affiliation.image).filter(Boolean) ?? []);
verifiedMeta?.affiliations
.map((affiliation) => affiliation.image)
.filter(Boolean)
.slice(0, 5) ?? [];
const organizationCount = Math.min(verifiedMeta?.affiliations.length ?? 0, 5);
const organizationLogoDataUrls = (
await Promise.all(
organizationImageUrls.map(async (imageUrl) => {
@@ -100,12 +119,13 @@ export default defineEventHandler(async (event) => {
const svg = buildPublisherOgSvg({
clawHubLogoDataUrl,
avatarDataUrl,
avatarShape: kindFromQuery === "org" || meta?.kind === "org" ? "rounded" : "circle",
official: cleanString(query.official) ? readBooleanQuery(query.official) : meta?.official,
avatarShape: avatarKind === "org" ? "rounded" : "circle",
official: verifiedMeta?.official ?? false,
title,
handleLabel,
organizationCount,
organizationLogos: organizationLogoDataUrls,
stats: [buildOgDownloadsStat(resolveOgDownloadsDisplay(query, meta?.stats.downloads))],
stats: [buildOgDownloadsStat(resolveOgDownloadsDisplay(statsQuery, meta?.stats.downloads))],
});
const resvg = new Resvg(svg, {
+39
View File
@@ -16,6 +16,9 @@ async function loadRoute() {
return (await import("../routes/$slug")).Route as unknown as {
__config: {
loader: (args: { params: { slug: string } }) => Promise<unknown>;
head: (args: { params: { slug: string }; loaderData?: unknown }) => {
meta?: Array<{ property?: string; name?: string; content: string }>;
};
};
};
}
@@ -58,6 +61,42 @@ describe("top-level slug route loader", () => {
});
});
it("includes official and affiliation metadata in canonical publisher OG images", async () => {
const route = await loadRoute();
const head = route.__config.head({
params: { slug: "nvidia" },
loaderData: {
publisher: {
_id: "publishers:nvidia",
handle: "nvidia",
displayName: "NVIDIA",
bio: "Official NVIDIA publisher.",
image: "https://example.com/nvidia.png",
kind: "org",
official: true,
affiliations: [
{
publisher: {
displayName: "OpenClaw",
image: "https://example.com/openclaw.png",
},
role: "publisher",
},
],
stats: {
downloads: 1200,
},
},
},
});
const image = head.meta?.find((item) => item.property === "og:image")?.content ?? "";
expect(image).toContain("/og/profile?");
expect(image).toContain("official=1");
expect(image).toContain("orgState=1");
expect(image).toContain("orgImages=https%3A%2F%2Fexample.com%2Fopenclaw.png");
});
it("returns not found for unknown slugs", async () => {
resolveTopLevelSlugRouteMock.mockResolvedValue(null);
+39
View File
@@ -24,6 +24,9 @@ async function loadRoute() {
return (await import("../routes/user/$handle")).Route as unknown as {
__config: {
loader?: (args: { params: { handle: string } }) => Promise<unknown>;
head?: (args: { params: { handle: string }; loaderData?: unknown }) => {
meta?: Array<{ property?: string; name?: string; content: string }>;
};
};
};
}
@@ -57,4 +60,40 @@ describe("user profile route loader", () => {
publisher: { _id: "publishers:active", handle: "active" },
});
});
it("includes official and affiliation metadata in legacy publisher OG images", async () => {
const route = await loadRoute();
const head = route.__config.head?.({
params: { handle: "teoslayer" },
loaderData: {
publisher: {
_id: "publishers:teoslayer",
handle: "teoslayer",
displayName: "Calin Teodor",
bio: "Publisher @teoslayer on ClawHub.",
image: "https://example.com/avatar.png",
kind: "user",
official: true,
affiliations: [
{
publisher: {
displayName: "OpenClaw",
image: "https://example.com/openclaw.png",
},
role: "publisher",
},
],
stats: {
downloads: 73878,
},
},
},
});
const image = head?.meta?.find((item) => item.property === "og:image")?.content ?? "";
expect(image).toContain("/og/profile?");
expect(image).toContain("official=1");
expect(image).toContain("orgState=1");
expect(image).toContain("orgImages=https%3A%2F%2Fexample.com%2Fopenclaw.png");
});
});
+1 -1
View File
@@ -64,7 +64,7 @@ describe("og helpers", () => {
expect(meta.image).toContain("v=8");
expect(meta.image).toContain("handle=byungkyu");
expect(meta.image).toContain("title=byungkyu");
expect(meta.image).toContain("description=maton.ai");
expect(meta.image).not.toContain("description=");
expect(meta.image).toContain("kind=org");
expect(meta.image).toContain("official=1");
expect(meta.image).toContain("orgState=1");
+2 -3
View File
@@ -34,8 +34,8 @@ type PublisherMetaSource = {
bio?: string | null;
image?: string | null;
kind?: "user" | "org";
official?: boolean | null;
affiliations?: Array<{
official: boolean | null;
affiliations: Array<{
publisher?: {
displayName?: string | null;
image?: string | null;
@@ -146,7 +146,6 @@ export function buildPublisherMeta(source: PublisherMetaSource): BasicMeta {
imageParams.set("v", OG_PUBLISHER_IMAGE_LAYOUT_VERSION);
imageParams.set("handle", handle);
imageParams.set("title", displayName);
imageParams.set("description", truncate(description, 200));
if (source.kind === "org") imageParams.set("kind", "org");
imageParams.set("official", source.official ? "1" : "0");
const organizationCount = source.affiliations?.length ?? 0;
+2
View File
@@ -21,6 +21,8 @@ export const Route = createFileRoute("/$slug")({
bio: publisher.bio,
image: publisher.image,
kind: publisher.kind,
official: publisher.official ?? null,
affiliations: publisher.affiliations ?? null,
downloads: publisher.stats.downloads,
});
return {
+2 -2
View File
@@ -104,8 +104,8 @@ export const Route = createFileRoute("/user/$handle")({
bio: publisher?.bio,
image: publisher?.image,
kind: publisher?.kind,
official: publisher?.official,
affiliations: publisher?.affiliations,
official: publisher?.official ?? null,
affiliations: publisher?.affiliations ?? null,
downloads: publisher?.stats.downloads,
});
return {