History

refactor(plugins/react): deduplicate favicon URL resolution

Replace inline getPreviewFaviconUrl implementations in GraphQL, MCP, and OpenAPI form components with getFaviconUrlForRemoteUrl from shared module. Also applies useEffectEvent fix (openapi) and type annotation improvement (mcp).

Saatvik Arya committed 7 months ago

Commit 27ebe861 parent e1281843 files · +12 −178Browse files

Unified · Split

3 files
  1. plugins/graphql/react/components.tsx +2 −58
  2. plugins/mcp/react/components.tsx +3 −59
  3. plugins/openapi/react/components.tsx +7 −61
plugins/graphql/react/components.tsx+2−58
@@ -35,6 +35,7 @@ import { · 34 unmodified lines
3535 graphqlHttpApiExtension,
3636 } from "@executor/plugin-graphql-http";
3737 import {
38+ getFaviconUrlForRemoteUrl,
3839 type GraphqlConnectInput,
3940 type GraphqlConnectionAuth,
4041 } from "@executor/plugin-graphql-shared";
@@ -62,63 +63,6 @@ const defaultGraphqlInput = (): GraphqlConnectInput => ({ · 21 unmodified lines
6263 const DEFAULT_BEARER_HEADER_NAME = "Authorization";
6364 const DEFAULT_BEARER_PREFIX = "Bearer ";
6465
65−const COMMON_COMPOUND_SUFFIXES = new Set([
66− "ac.uk",
67− "co.in",
68− "co.jp",
69− "co.nz",
70− "co.uk",
71− "com.au",
72− "com.br",
73− "com.mx",
74− "net.au",
75− "org.au",
76− "org.uk",
77−]);
78−
79−const isIpv4Address = (value: string): boolean =>
80− /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value);
81−
82−const toRegistrableDomain = (hostname: string): string | null => {
83− const normalized = hostname.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
84− if (!normalized) {
85− return null;
86− }
87−
88− if (normalized === "localhost" || isIpv4Address(normalized)) {
89− return normalized;
90− }
91−
92− const parts = normalized.split(".").filter((part) => part.length > 0);
93− if (parts.length < 2) {
94− return null;
95− }
96−
97− const suffix = parts.slice(-2).join(".");
98− if (parts.length >= 3 && COMMON_COMPOUND_SUFFIXES.has(suffix)) {
99− return parts.slice(-3).join(".");
100− }
101−
102− return parts.slice(-2).join(".");
103−};
104−
105−const getPreviewFaviconUrl = (value: string): string | null => {
106− const trimmed = value.trim();
107− if (!trimmed) {
108− return null;
109− }
110−
111− try {
112− const url = new URL(trimmed);
113− const domain = toRegistrableDomain(url.hostname);
114− return domain
115− ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=32`
116− : null;
117− } catch {
118− return null;
119− }
120−};
121−
12266 const presetString = (
12367 search: Record<string, unknown>,
12468 key: string,
@@ -270,7 +214,7 @@ function GraphqlSourceForm(props: { · 145 unmodified lines
270214 bearerPrefixValue(props.initialValue.auth),
271215 );
272216 const [error, setError] = useState<string | null>(null);
273− const resolvedIconUrl = iconUrl.trim() || getPreviewFaviconUrl(endpoint);
217+ const resolvedIconUrl = iconUrl.trim() || getFaviconUrlForRemoteUrl(endpoint);
274218
275219 return (
276220 <div className="space-y-6 rounded-lg border border-border bg-card p-6 text-sm ring-1 ring-foreground/[0.04]">
plugins/mcp/react/components.tsx+3−59
@@ -30,6 +30,7 @@ import { · 29 unmodified lines
3030 mcpHttpApiExtension,
3131 } from "@executor/plugin-mcp-http";
3232 import {
33+ getFaviconUrlForRemoteUrl,
3334 type McpConnectInput,
3435 type McpConnectionAuth,
3536 type McpOAuthPopupResult,
@@ -110,63 +111,6 @@ const presetJsonStringArray = ( · 74 unmodified lines
110111 return value ? parseJsonStringArray(key, value) : null;
111112 };
112113
113−const COMMON_COMPOUND_SUFFIXES = new Set([
114− "ac.uk",
115− "co.in",
116− "co.jp",
117− "co.nz",
118− "co.uk",
119− "com.au",
120− "com.br",
121− "com.mx",
122− "net.au",
123− "org.au",
124− "org.uk",
125−]);
126−
127−const isIpv4Address = (value: string): boolean =>
128− /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value);
129−
130−const toRegistrableDomain = (hostname: string): string | null => {
131− const normalized = hostname.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
132− if (!normalized) {
133− return null;
134− }
135−
136− if (normalized === "localhost" || isIpv4Address(normalized)) {
137− return normalized;
138− }
139−
140− const parts = normalized.split(".").filter((part) => part.length > 0);
141− if (parts.length < 2) {
142− return null;
143− }
144−
145− const suffix = parts.slice(-2).join(".");
146− if (parts.length >= 3 && COMMON_COMPOUND_SUFFIXES.has(suffix)) {
147− return parts.slice(-3).join(".");
148− }
149−
150− return parts.slice(-2).join(".");
151−};
152−
153−const getPreviewFaviconUrl = (value: string): string | null => {
154− const trimmed = value.trim();
155− if (!trimmed) {
156− return null;
157− }
158−
159− try {
160− const url = new URL(trimmed);
161− const domain = toRegistrableDomain(url.hostname);
162− return domain
163− ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=32`
164− : null;
165− } catch {
166− return null;
167− }
168−};
169−
170114 const mcpInputFromSearch = (
171115 search: Record<string, unknown>,
172116 ): McpConnectInput => {
@@ -347,7 +291,7 @@ function McpSourceForm(props: { · 174 unmodified lines
347291
348292 const isStdio = transportFields.transport === "stdio";
349293 const resolvedIconUrl = iconUrl.trim()
350− || (!isStdio ? getPreviewFaviconUrl(endpoint) : null);
294+ || (!isStdio ? getFaviconUrlForRemoteUrl(endpoint) : null);
351295
352296 const runOauth = async () => {
353297 if (installation.status !== "ready") {
@@ -371,7 +315,7 @@ function McpSourceForm(props: { · 17 unmodified lines
371315 ).toString(),
372316 };
373317
374− let started;
318+ let started: Awaited<ReturnType<typeof startOAuth>>;
375319 try {
376320 started = await startOAuth({
377321 path: {
plugins/openapi/react/components.tsx+7−61
@@ -30,6 +30,9 @@ import { · 29 unmodified lines
3030 import {
3131 openApiHttpApiExtension,
3232 } from "@executor/plugin-openapi-http";
33+import {
34+ getFaviconUrlForRemoteUrl,
35+} from "@executor/plugin-openapi-shared";
3336 import type {
3437 OpenApiConnectInput,
3538 OpenApiPreviewRequest,
@@ -37,7 +40,7 @@ import type { · 1 unmodified line
3740 OpenApiPreviewResponse,
3841 OpenApiSourceConfigPayload,
3942 } from "@executor/plugin-openapi-shared";
40−import { startTransition, useEffect, useState, type ReactNode } from "react";
43+import { startTransition, useEffect, useEffectEvent, useState, type ReactNode } from "react";
4144
4245 type RouteToolSearch = SourceToolExplorerSearch;
4346
@@ -54,63 +57,6 @@ const defaultOpenApiInput = (): OpenApiConnectInput => ({ · 10 unmodified lines
5457 const DEFAULT_BEARER_HEADER_NAME = "Authorization";
5558 const DEFAULT_BEARER_PREFIX = "Bearer ";
5659
57−const COMMON_COMPOUND_SUFFIXES = new Set([
58− "ac.uk",
59− "co.in",
60− "co.jp",
61− "co.nz",
62− "co.uk",
63− "com.au",
64− "com.br",
65− "com.mx",
66− "net.au",
67− "org.au",
68− "org.uk",
69−]);
70−
71−const isIpv4Address = (value: string): boolean =>
72− /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value);
73−
74−const toRegistrableDomain = (hostname: string): string | null => {
75− const normalized = hostname.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
76− if (!normalized) {
77− return null;
78− }
79−
80− if (normalized === "localhost" || isIpv4Address(normalized)) {
81− return normalized;
82− }
83−
84− const parts = normalized.split(".").filter((part) => part.length > 0);
85− if (parts.length < 2) {
86− return null;
87− }
88−
89− const suffix = parts.slice(-2).join(".");
90− if (parts.length >= 3 && COMMON_COMPOUND_SUFFIXES.has(suffix)) {
91− return parts.slice(-3).join(".");
92− }
93−
94− return parts.slice(-2).join(".");
95−};
96−
97−const getPreviewFaviconUrl = (value: string | null | undefined): string | null => {
98− const trimmed = value?.trim();
99− if (!trimmed) {
100− return null;
101− }
102−
103− try {
104− const url = new URL(trimmed);
105− const domain = toRegistrableDomain(url.hostname);
106− return domain
107− ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=32`
108− : null;
109− } catch {
110− return null;
111− }
112−};
113−
11460 const presetString = (
11561 search: Record<string, unknown>,
11662 key: string,
@@ -342,9 +288,9 @@ function OpenApiSourceForm(props: { · 225 unmodified lines
342288 })
343289 );
344290 const submitMutation = useExecutorMutation<OpenApiConnectInput, void>(props.onSubmit);
345− const resolvedIconUrl = iconUrl.trim() || getPreviewFaviconUrl(baseUrl || specUrl);
291+ const resolvedIconUrl = iconUrl.trim() || getFaviconUrlForRemoteUrl(baseUrl || specUrl);
346292
347− const runPreview = async (input: {
293+ const runPreview = useEffectEvent(async (input: {
348294 mode: "auto" | "manual";
349295 }) => {
350296 const trimmedSpecUrl = specUrl.trim();
@@ -387,7 +333,7 @@ function OpenApiSourceForm(props: { · 36 unmodified lines
387333 }
388334 setPreview(null);
389335 }
390− };
336+ });
391337
392338 useEffect(() => {
393339 const trimmedSpecUrl = specUrl.trim();