Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/plugins/graphql/src/sdk/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const graphqlPresets: readonly GraphqlPreset[] = [
summary: "Projects, merge requests, pipelines, and users.",
url: "https://gitlab.com/api/graphql",
endpoint: "https://gitlab.com/api/graphql",
icon: "https://gitlab.com/favicon.ico",
icon: "https://integrations.sh/logo/gitlab.com",
featured: true,
},
{
Expand All @@ -33,7 +33,7 @@ export const graphqlPresets: readonly GraphqlPreset[] = [
summary: "Issues, projects, teams, and cycles.",
url: "https://api.linear.app/graphql",
endpoint: "https://api.linear.app/graphql",
icon: "https://linear.app/favicon.ico",
icon: "https://integrations.sh/logo/linear.app",
featured: true,
},
{
Expand All @@ -42,14 +42,14 @@ export const graphqlPresets: readonly GraphqlPreset[] = [
summary: "Boards, items, columns, and workspace automation.",
url: "https://api.monday.com/v2",
endpoint: "https://api.monday.com/v2",
icon: "https://monday.com/favicon.ico",
icon: "https://integrations.sh/logo/monday.com",
},
{
id: "anilist",
name: "AniList",
summary: "Anime and manga database — no auth required.",
url: "https://graphql.anilist.co",
endpoint: "https://graphql.anilist.co",
icon: "https://anilist.co/img/icons/favicon-32x32.png",
icon: "https://integrations.sh/logo/anilist.co",
},
];
20 changes: 10 additions & 10 deletions packages/plugins/mcp/src/sdk/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Search and read documentation from any GitHub repo.",
url: "https://mcp.deepwiki.com/mcp",
endpoint: "https://mcp.deepwiki.com/mcp",
icon: "https://deepwiki.com/favicon.ico",
icon: "https://integrations.sh/logo/deepwiki.com",
featured: true,
},
{
Expand All @@ -47,7 +47,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Up-to-date docs and code examples for any library.",
url: "https://mcp.context7.com/mcp",
endpoint: "https://mcp.context7.com/mcp",
icon: "https://context7.com/favicon.ico",
icon: "https://integrations.sh/logo/context7.com",
featured: true,
},
{
Expand All @@ -56,7 +56,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Cloud browser sessions for web scraping and automation.",
url: "https://mcp.browserbase.com/mcp",
endpoint: "https://mcp.browserbase.com/mcp",
icon: "https://www.browserbase.com/favicon.ico",
icon: "https://integrations.sh/logo/browserbase.com",
featured: true,
},
{
Expand All @@ -65,7 +65,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Crawl and scrape websites into structured data.",
url: "https://mcp.firecrawl.dev/mcp",
endpoint: "https://mcp.firecrawl.dev/mcp",
icon: "https://www.firecrawl.dev/favicon.ico",
icon: "https://integrations.sh/logo/firecrawl.dev",
featured: true,
},
{
Expand All @@ -74,7 +74,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Serverless Postgres — branches, queries, and management.",
url: "https://mcp.neon.tech/mcp",
endpoint: "https://mcp.neon.tech/mcp",
icon: "https://neon.tech/favicon/favicon.ico",
icon: "https://integrations.sh/logo/neon.tech",
featured: true,
},
{
Expand All @@ -83,7 +83,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Query, analyze, and monitor your logs and event data.",
url: "https://mcp.axiom.co/mcp",
endpoint: "https://mcp.axiom.co/mcp",
icon: "https://axiom.co/favicon.ico",
icon: "https://integrations.sh/logo/axiom.co",
featured: true,
},
{
Expand All @@ -92,7 +92,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Manage payments, subscriptions, and billing via MCP.",
url: "https://mcp.stripe.com",
endpoint: "https://mcp.stripe.com",
icon: "https://stripe.com/favicon.ico",
icon: "https://integrations.sh/logo/stripe.com",
featured: true,
},
{
Expand All @@ -101,7 +101,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Issues, projects, teams, and cycles via MCP.",
url: "https://mcp.linear.app/mcp",
endpoint: "https://mcp.linear.app/mcp",
icon: "https://linear.app/favicon.ico",
icon: "https://integrations.sh/logo/linear.app",
featured: true,
},
{
Expand All @@ -110,7 +110,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Databases, pages, blocks, and search via MCP.",
url: "https://mcp.notion.com/mcp",
endpoint: "https://mcp.notion.com/mcp",
icon: "https://www.notion.com/front-static/favicon.ico",
icon: "https://integrations.sh/logo/notion.com",
featured: true,
},
{
Expand All @@ -127,7 +127,7 @@ export const mcpPresets: readonly McpPreset[] = [
summary: "Workers, KV, D1, R2, and DNS management via MCP.",
url: "https://mcp.cloudflare.com/mcp",
endpoint: "https://mcp.cloudflare.com/mcp",
icon: "https://cloudflare.com/favicon.ico",
icon: "https://integrations.sh/logo/cloudflare.com",
},
{
id: "chrome-devtools",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/microsoft/src/sdk/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface MicrosoftGraphScopePreset {
readonly audience: MicrosoftGraphScopeAudience;
}

const MICROSOFT_ICON = "https://www.microsoft.com/favicon.ico";
const MICROSOFT_ICON = "https://integrations.sh/logo/microsoft.com";
const svglIcon = (name: string) => `https://svgl.app/library/${name}.svg`;

export const MICROSOFT_GRAPH_OPENAPI_URL =
Expand Down
26 changes: 13 additions & 13 deletions packages/plugins/openapi/src/sdk/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const openApiOnlyPresets: readonly OpenApiPreset[] = [
name: "Stripe",
summary: "Payments, subscriptions, customers, and invoices.",
url: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json",
icon: "https://stripe.com/favicon.ico",
icon: "https://integrations.sh/logo/stripe.com",
featured: true,
},
{
Expand All @@ -29,23 +29,23 @@ const openApiOnlyPresets: readonly OpenApiPreset[] = [
name: "Vercel",
summary: "Deployments, domains, projects, and edge config.",
url: "https://openapi.vercel.sh",
icon: "https://vercel.com/favicon.ico",
icon: "https://integrations.sh/logo/vercel.com",
featured: true,
},
{
id: "cloudflare",
name: "Cloudflare",
summary: "DNS, workers, pages, R2, and security rules.",
url: "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json",
icon: "https://cloudflare.com/favicon.ico",
icon: "https://integrations.sh/logo/cloudflare.com",
featured: true,
},
{
id: "neon",
name: "Neon",
summary: "Serverless Postgres: projects, branches, and endpoints.",
url: "https://neon.tech/api_spec/release/v2.json",
icon: "https://neon.tech/favicon/favicon.ico",
icon: "https://integrations.sh/logo/neon.tech",
featured: true,
},
{
Expand Down Expand Up @@ -77,65 +77,65 @@ const openApiOnlyPresets: readonly OpenApiPreset[] = [
name: "Exa",
summary: "Web search, similar links, content retrieval, and answers.",
url: "https://raw.githubusercontent.com/exa-labs/openapi-spec/refs/heads/master/exa-openapi-spec.yaml",
icon: "https://exa.ai/images/favicon-32x32.png",
icon: "https://integrations.sh/logo/exa.ai",
featured: true,
},
{
id: "exa-websets",
name: "Exa Websets",
summary: "Websets, enrichments, webhooks, and monitors.",
url: "https://raw.githubusercontent.com/exa-labs/openapi-spec/refs/heads/master/exa-websets-spec.yaml",
icon: "https://exa.ai/images/favicon-32x32.png",
icon: "https://integrations.sh/logo/exa.ai",
featured: true,
},
{
id: "axiom",
name: "Axiom",
summary: "Log ingestion, querying, datasets, and monitors.",
url: "https://axiom.co/docs/restapi/versions/v2.json",
icon: "https://axiom.co/favicon.ico",
icon: "https://integrations.sh/logo/axiom.co",
},
{
id: "asana",
name: "Asana",
summary: "Tasks, projects, teams, and workspace management.",
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/asana.com/1.0/openapi.yaml",
icon: "https://asana.com/favicon.ico",
icon: "https://integrations.sh/logo/asana.com",
},
{
id: "twilio",
name: "Twilio",
summary: "SMS, voice, video, and messaging APIs.",
url: "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json",
icon: "https://twilio.com/favicon.ico",
icon: "https://integrations.sh/logo/twilio.com",
},
{
id: "digitalocean",
name: "DigitalOcean",
summary: "Droplets, Kubernetes, databases, and networking.",
url: "https://raw.githubusercontent.com/digitalocean/openapi/main/specification/DigitalOcean-public.v2.yaml",
icon: "https://assets.digitalocean.com/favicon.ico",
icon: "https://integrations.sh/logo/digitalocean.com",
},
{
id: "petstore",
name: "Petstore",
summary: "Classic OpenAPI demo, no auth required.",
url: "https://petstore3.swagger.io/api/v3/openapi.json",
icon: "https://petstore3.swagger.io/favicon-32x32.png",
icon: "https://integrations.sh/logo/petstore3.swagger.io",
},
{
id: "val-town",
name: "Val Town",
summary: "Vals, runs, blobs, and email/web endpoints.",
url: "https://api.val.town/openapi.json",
icon: "https://www.val.town/favicon.svg",
icon: "https://integrations.sh/logo/val.town",
},
{
id: "resend",
name: "Resend",
summary: "Transactional email sending and domain management.",
url: "https://raw.githubusercontent.com/resend/resend-openapi/main/resend.yaml",
icon: "https://resend.com/static/favicons/favicon.ico",
icon: "https://integrations.sh/logo/resend.com",
},
{
id: "spotify",
Expand Down
21 changes: 19 additions & 2 deletions packages/react/src/components/integration-favicon.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from "@effect/vitest";

import {
integrationFaviconFallbackUrl,
integrationFaviconSrc,
integrationFaviconUrl,
integrationInferredUrl,
Expand All @@ -9,19 +10,35 @@ import {
} from "./integration-favicon";

describe("IntegrationFavicon", () => {
it("uses the favicon service that handles provider-specific icon locations", () => {
it("uses the integrations.sh logo proxy as the primary favicon source", () => {
expect(integrationFaviconUrl("https://api.github.com/graphql", 20)).toBe(
"https://integrations.sh/logo/github.com?sz=40",
);
});

it("falls back to the Google favicon service", () => {
expect(integrationFaviconFallbackUrl("https://api.github.com/graphql", 20)).toBe(
"https://www.google.com/s2/favicons?domain=github.com&sz=40",
);
});

it("does not request favicons for local URLs", () => {
expect(integrationFaviconUrl("http://localhost:3000/private", 20)).toBeNull();
expect(integrationFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull();
expect(integrationFaviconFallbackUrl("http://localhost:3000/private", 20)).toBeNull();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The 127.0.0.1 local-URL guard is tested for integrationFaviconUrl but the new integrationFaviconFallbackUrl is only checked against localhost. Since both functions share the same integrationFaviconDomain helper the guard holds for IPs too, but the symmetry gap makes it easy for a future refactor to silently break IP blocking on the fallback path without a failing test.

Suggested change
expect(integrationFaviconFallbackUrl("http://localhost:3000/private", 20)).toBeNull();
expect(integrationFaviconFallbackUrl("http://localhost:3000/private", 20)).toBeNull();
expect(integrationFaviconFallbackUrl("http://127.0.0.1:3000/private", 20)).toBeNull();

});

it("sends only the registrable domain to the favicon service", () => {
it("sends only the registrable domain to the logo proxy", () => {
expect(integrationFaviconUrl("https://api.github.com/private", 20)).toBe(
"https://integrations.sh/logo/github.com?sz=40",
);
});

it("walks the cascade from the proxy to the favicon service on failure", () => {
const url = "https://api.github.com/graphql";
const primary = integrationFaviconSrc({ url, size: 20 });
expect(primary).toBe("https://integrations.sh/logo/github.com?sz=40");
expect(integrationFaviconSrc({ url, size: 20, failedSrcs: [primary ?? ""] })).toBe(
"https://www.google.com/s2/favicons?domain=github.com&sz=40",
);
});
Expand Down
31 changes: 25 additions & 6 deletions packages/react/src/components/integration-favicon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,26 @@ import { getDomain } from "tldts";
// Falls back to a neutral icon if the URL is missing or the image fails to load.
// ---------------------------------------------------------------------------

export function integrationFaviconUrl(url: string | undefined, size: number): string | null {
const integrationFaviconDomain = (url: string | undefined): string | null => {
if (!url) return null;
const domain = getDomain(url) ?? (URL.canParse(url) ? getDomain(new URL(url).hostname) : null);
return getDomain(url) ?? (URL.canParse(url) ? getDomain(new URL(url).hostname) : null);
};

// integrations.sh/logo proxies context.dev's Logo Link behind an edge cache
// and is executor's single logo source; Google's favicon service remains in
// the cascade as the fallback the <img> onError walks to when the proxy is
// unreachable or serves nothing for the domain.
export function integrationFaviconUrl(url: string | undefined, size: number): string | null {
const domain = integrationFaviconDomain(url);
if (!domain) return null;
return `https://integrations.sh/logo/${domain}?sz=${size * 2}`;
}

export function integrationFaviconFallbackUrl(
url: string | undefined,
size: number,
): string | null {
const domain = integrationFaviconDomain(url);
if (!domain) return null;
return `https://www.google.com/s2/favicons?domain=${domain}&sz=${size * 2}`;
}
Expand Down Expand Up @@ -161,10 +178,11 @@ export function integrationPresetIconUrl(
}

// Resolution cascade for the rendered favicon: first non-null, non-failed of an
// explicit preset icon, the bundled local icon for a known source id, then a
// favicon-service URL derived from the source URL. The built-in executor source
// has no preset icon and no URL, so it resolves ONLY through the sourceId branch:
// callers that drop sourceId fall through to the neutral BoxIcon placeholder.
// explicit preset icon, the bundled local icon for a known source id, the
// integrations.sh logo proxy derived from the source URL, then the Google
// favicon service as a last resort. The built-in executor source has no preset
// icon and no URL, so it resolves ONLY through the sourceId branch: callers
// that drop sourceId fall through to the neutral BoxIcon placeholder.
export function integrationFaviconSrc(args: {
icon?: string | null;
sourceId?: string;
Expand All @@ -178,6 +196,7 @@ export function integrationFaviconSrc(args: {
args.icon ?? null,
integrationLocalIconUrl(args.sourceId),
integrationFaviconUrl(args.url, args.size),
integrationFaviconFallbackUrl(args.url, args.size),
].find((candidate) => candidate !== null && !failedSrcs.includes(candidate)) ?? null
);
}
Expand Down
Loading