remote-components 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/{component-loader-26b1f55e.d.ts → component-loader-1838f572.d.ts} +62 -8
  2. package/dist/html/host.cjs +165 -70
  3. package/dist/html/host.cjs.map +1 -1
  4. package/dist/html/host.js +165 -70
  5. package/dist/html/host.js.map +1 -1
  6. package/dist/internal/next/host/app-router-client.cjs +13 -50
  7. package/dist/internal/next/host/app-router-client.cjs.map +1 -1
  8. package/dist/internal/next/host/app-router-client.d.ts +1 -1
  9. package/dist/internal/next/host/app-router-client.js +14 -51
  10. package/dist/internal/next/host/app-router-client.js.map +1 -1
  11. package/dist/internal/next/host/remote-component-links.cjs +96 -0
  12. package/dist/internal/next/host/remote-component-links.cjs.map +1 -0
  13. package/dist/internal/next/host/remote-component-links.d.ts +25 -0
  14. package/dist/internal/next/host/remote-component-links.js +72 -0
  15. package/dist/internal/next/host/remote-component-links.js.map +1 -0
  16. package/dist/internal/shared/client/remote-component.cjs +17 -1
  17. package/dist/internal/shared/client/remote-component.cjs.map +1 -1
  18. package/dist/internal/shared/client/remote-component.d.ts +2 -2
  19. package/dist/internal/shared/client/remote-component.js +17 -1
  20. package/dist/internal/shared/client/remote-component.js.map +1 -1
  21. package/dist/internal/shared/ssr/dom-flight.d.ts +1 -1
  22. package/dist/internal/shared/ssr/fetch-remote-component.d.ts +1 -1
  23. package/dist/internal/shared/ssr/fetch-with-hooks.cjs +13 -3
  24. package/dist/internal/shared/ssr/fetch-with-hooks.cjs.map +1 -1
  25. package/dist/internal/shared/ssr/fetch-with-hooks.d.ts +24 -13
  26. package/dist/internal/shared/ssr/fetch-with-hooks.js +13 -3
  27. package/dist/internal/shared/ssr/fetch-with-hooks.js.map +1 -1
  28. package/dist/internal/shared/ssr/fetch-with-protected-rc-fallback.cjs +6 -1
  29. package/dist/internal/shared/ssr/fetch-with-protected-rc-fallback.cjs.map +1 -1
  30. package/dist/internal/shared/ssr/fetch-with-protected-rc-fallback.d.ts +3 -0
  31. package/dist/internal/shared/ssr/fetch-with-protected-rc-fallback.js +6 -1
  32. package/dist/internal/shared/ssr/fetch-with-protected-rc-fallback.js.map +1 -1
  33. package/dist/internal/shared/utils/abort.cjs +38 -0
  34. package/dist/internal/shared/utils/abort.cjs.map +1 -0
  35. package/dist/internal/shared/utils/abort.d.ts +7 -0
  36. package/dist/internal/shared/utils/abort.js +14 -0
  37. package/dist/internal/shared/utils/abort.js.map +1 -0
  38. package/dist/next/config.cjs +4 -2
  39. package/dist/next/config.cjs.map +1 -1
  40. package/dist/next/config.js +4 -2
  41. package/dist/next/config.js.map +1 -1
  42. package/dist/next/host/app-router-server.d.ts +1 -1
  43. package/dist/next/host/client/index.cjs +41 -8
  44. package/dist/next/host/client/index.cjs.map +1 -1
  45. package/dist/next/host/client/index.d.ts +1 -1
  46. package/dist/next/host/client/index.js +41 -8
  47. package/dist/next/host/client/index.js.map +1 -1
  48. package/dist/next/host/pages-router-client.d.ts +1 -1
  49. package/dist/next/host/pages-router-server.d.ts +1 -1
  50. package/dist/next/index.d.ts +1 -1
  51. package/dist/next/proxy.cjs.map +1 -1
  52. package/dist/next/proxy.js.map +1 -1
  53. package/dist/react/index.cjs +41 -8
  54. package/dist/react/index.cjs.map +1 -1
  55. package/dist/react/index.d.ts +2 -1
  56. package/dist/react/index.js +41 -8
  57. package/dist/react/index.js.map +1 -1
  58. package/dist/{types-6e4ba234.d.ts → types-cbe44b51.d.ts} +61 -7
  59. package/dist/webpack.cjs +274 -0
  60. package/dist/webpack.cjs.map +1 -0
  61. package/dist/webpack.d.ts +14 -0
  62. package/dist/webpack.js +247 -0
  63. package/dist/webpack.js.map +1 -0
  64. package/package.json +9 -1
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { O as OnRequestHook, a as OnResponseHook } from '../../types-6e4ba234.js';
2
+ import { O as OnRequestHook, a as OnResponseHook } from '../../types-cbe44b51.js';
3
3
  import 'parse5/dist/tree-adapters/default';
4
4
 
5
5
  declare module 'react/jsx-runtime' {
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { O as OnRequestHook, a as OnResponseHook } from '../../types-6e4ba234.js';
2
+ import { O as OnRequestHook, a as OnResponseHook } from '../../types-cbe44b51.js';
3
3
  import 'parse5/dist/tree-adapters/default';
4
4
 
5
5
  declare const REMOTE_COMPONENT_KEY = "__REMOTE_COMPONENT_KEY__";
@@ -1,6 +1,6 @@
1
1
  import { RemoteComponent as RemoteComponent$2 } from '../internal/next/remote/render-server.js';
2
2
  import { RemoteComponent as RemoteComponent$1 } from './host/app-router-server.js';
3
- import '../types-6e4ba234.js';
3
+ import '../types-cbe44b51.js';
4
4
  import 'parse5/dist/tree-adapters/default';
5
5
 
6
6
  type RemoteComponentAppServerProps = Parameters<typeof RemoteComponent$1>[0];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/proxy/index.ts","../../src/next/proxy/withRemoteComponents.ts","../../src/shared/remote/proxy.ts","../../src/next/proxy/withRemoteComponentsHost.ts","../../src/shared/ssr/fetch-headers.ts","../../src/shared/utils/logger.ts","../../src/shared/ssr/fetch-with-protected-rc-fallback.ts","../../src/shared/host/proxy.ts"],"sourcesContent":["export {\n type RemoteComponentProxyOptions,\n withRemoteComponents,\n} from './withRemoteComponents';\nexport { withRemoteComponentsHost } from './withRemoteComponentsHost';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n getCorsHeaders,\n handleCorsPreflightRequest,\n type RemoteComponentProxyOptions,\n} from '#internal/shared/remote/proxy';\n\n/**\n * This proxy is used to handle CORS and other remote component related tasks.\n * It can be used to wrap a Next.js proxy handler function to add CORS headers and handle preflight requests.\n *\n * @param proxy - The Next.js proxy handler function to wrap.\n * @param options - Optional configuration for handling remote components.\n * @returns A Next.js proxy handler function that handles CORS and preflight requests\n */\nexport function withRemoteComponents(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: RemoteComponentProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a CORS preflight request\n const preflightResponse = handleCorsPreflightRequest(\n request.method,\n request.headers,\n options?.cors,\n );\n\n if (preflightResponse) {\n return preflightResponse;\n }\n\n // For all other requests, continue and attach CORS headers\n const response =\n typeof proxy === 'function' ? await proxy(request) : NextResponse.next();\n\n if (options?.cors !== false) {\n const corsHeaders = getCorsHeaders(options?.cors, request.headers);\n Object.entries(corsHeaders).forEach(([k, v]) =>\n response.headers.set(k, v),\n );\n }\n\n return response;\n };\n}\n\nexport type { RemoteComponentProxyOptions };\n","/**\n * Proxy utilities for remote applications that expose components to hosts.\n */\n\nimport type { IncomingHttpHeaders } from 'node:http';\n\nexport interface RemoteComponentProxyOptions {\n cors?:\n | {\n origin?: string | string[];\n method?: string | string[];\n headers?: string | string[];\n credentials?: boolean;\n maxAge?: string;\n }\n | false;\n}\n\n/**\n * Gets a header value from either a Headers object or a plain object.\n */\nexport function getHeader(\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n name: string,\n): string | undefined {\n if (headers instanceof Headers) {\n return headers.get(name) ?? undefined;\n }\n const value = headers[name];\n // IncomingHttpHeaders can have string | string[] | undefined\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Computes CORS headers based on the provided options and request headers.\n *\n * @param options - CORS configuration options\n * @param requestHeaders - Headers from the incoming request (can be a Headers object or a plain object)\n * @returns Object containing CORS headers to be added to the response\n */\nexport function getCorsHeaders(\n options: RemoteComponentProxyOptions['cors'],\n requestHeaders: Record<string, string> | Headers | IncomingHttpHeaders,\n): Record<string, string> {\n if (options === false) {\n return {};\n }\n\n const originHeader = getHeader(requestHeaders, 'origin');\n const refererHeader = getHeader(requestHeaders, 'referer');\n const origin =\n originHeader ?? (refererHeader ? new URL(refererHeader).origin : '*');\n\n const ALLOWED_ORIGINS = (\n process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS ||\n (Array.isArray(options?.origin)\n ? options.origin.join(',')\n : options?.origin) ||\n '*'\n )\n .split(',')\n .map((allowedOrigin) => allowedOrigin.trim());\n\n const isAllowed =\n ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);\n\n const allowedHeaders =\n process.env.REMOTE_COMPONENTS_ALLOW_HEADERS ||\n (Array.isArray(options?.headers)\n ? options.headers.map((h) => h.trim()).join(',')\n : options?.headers) ||\n getHeader(requestHeaders, 'access-control-request-headers');\n\n const CORS_HEADERS = (\n isAllowed\n ? {\n 'Access-Control-Allow-Origin': origin,\n 'Access-Control-Allow-Methods':\n process.env.REMOTE_COMPONENTS_ALLOW_METHODS ||\n (Array.isArray(options?.method)\n ? options.method.map((m) => m.trim()).join(',')\n : options?.method) ||\n 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS',\n ...(allowedHeaders\n ? { 'Access-Control-Allow-Headers': allowedHeaders }\n : {}),\n ...(process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS ||\n options?.credentials\n ? {\n 'Access-Control-Allow-Credentials':\n process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || 'true',\n }\n : {}),\n 'Access-Control-Max-Age': options?.maxAge || '600',\n Vary: 'Origin',\n }\n : {}\n ) as Record<string, string>;\n\n return CORS_HEADERS;\n}\n\n/**\n * Handles CORS preflight OPTIONS requests.\n *\n * @param method - The HTTP method of the incoming request\n * @param headers - Headers from the incoming request (can be a Headers object or a plain object)\n * @param options - CORS configuration options\n * @returns Response object for the preflight request, or null if not a preflight\n */\nexport function handleCorsPreflightRequest(\n method: string,\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n options?: RemoteComponentProxyOptions['cors'],\n): Response | null {\n if (method !== 'OPTIONS' || options === false) {\n return null;\n }\n\n const corsHeaders = getCorsHeaders(options, headers);\n\n return new Response(undefined, {\n status: 200,\n headers: corsHeaders,\n });\n}\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n type HostProxyOptions,\n handleProtectedRemoteFetchRequest,\n} from '#internal/shared/host/proxy';\n\n/**\n * Only needed when accessing a remote component in a preview environment that\n * is protected with vercel deployment protection. If the remote component fetch\n * errors, it will attempt to fetch via this proxy which adds the protection\n * bypass header to the request.\n *\n * To use this proxy, the path `/rc-fetch-protected-remote` must be added to the\n * proxy/middleware matchers.\n *\n * @param proxy - Optional Next.js middleware function to run after checking for protected remote fetch\n * @param options - Host proxy configuration options for SSRF protection\n */\nexport function withRemoteComponentsHost(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: HostProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a protected remote fetch request\n const protectedFetchResponse = await handleProtectedRemoteFetchRequest(\n request.url,\n options,\n );\n\n if (protectedFetchResponse) {\n return protectedFetchResponse;\n }\n\n return typeof proxy === 'function'\n ? await proxy(request)\n : NextResponse.next();\n };\n}\n\nexport type { HostProxyOptions };\n","/**\n * The headers to use when fetching the remote component.\n */\nexport function remoteFetchHeaders() {\n return {\n /**\n * Authenticates deployment protection for the remote. Needed for SSR and SSG clients.\n * If the remote component uses vercel deployment protection, ensure the host and remote vercel\n * projects share a common automation bypass secret, and the shared secret is used as the\n * VERCEL_AUTOMATION_BYPASS_SECRET env var in the host project.\n */\n ...(typeof process === 'object' &&\n typeof process.env === 'object' &&\n typeof process.env.VERCEL_AUTOMATION_BYPASS_SECRET === 'string'\n ? {\n 'x-vercel-protection-bypass':\n process.env.VERCEL_AUTOMATION_BYPASS_SECRET,\n }\n : {}),\n Accept: 'text/html',\n };\n}\n","import { RemoteComponentsError } from '#internal/shared/error';\n\ntype LogLocation =\n | 'ChunkLoader'\n | 'ComponentLoader'\n | 'SharedModules'\n | 'WebpackAdapter'\n | 'TurbopackModule'\n | 'StaticLoader'\n | 'Polyfill'\n | 'HtmlRemote'\n | 'HtmlHost'\n | 'Config'\n | 'NextAppRouter'\n | 'NextAppRouterCompat'\n | 'FetchRemoteComponent';\n\nconst PREFIX = 'remote-components';\nconst DEBUG =\n typeof window !== 'undefined' && localStorage.getItem('RC_DEBUG') === 'true';\n\nexport function logDebug(location: LogLocation, message: string) {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.debug(`[${PREFIX}:${location}]: ${message}`);\n }\n}\n\nexport function logInfo(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.info(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logWarn(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.warn(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logError(\n location: LogLocation,\n message: string,\n cause?: unknown,\n) {\n // eslint-disable-next-line no-console\n console.error(\n new RemoteComponentsError(`[${PREFIX}:${location}]: ${message}`, {\n cause,\n }),\n );\n}\n","import { logError, logInfo } from '#internal/shared/utils/logger';\n\nexport const RC_PROTECTED_REMOTE_FETCH_PATHNAME = '/rc-fetch-protected-remote';\n\n/**\n * When a vercel host preview fetches a vercel protected remote preview on the\n * client, the request will reject as it's not possible to authenticate for the\n * protected deployment in a client side fetch - cookies cannot be shared across\n * domains. To enable previews, this request is proxied via the host where the\n * process.env.VERCEL_AUTOMATION_BYPASS_SECRET will be added.\n */\nexport async function fetchWithProtectedRcFallback(\n url: URL | string,\n init?: RequestInit,\n): Promise<Response> {\n try {\n const res = await fetch(url, init);\n return res;\n } catch (error) {\n if (\n typeof document === 'object' &&\n typeof document.location === 'object' &&\n document.location.origin !== new URL(url).origin\n ) {\n logInfo(\n 'FetchRemoteComponent',\n 'Request failed due to CORS, attempting to fetch it via the withRemoteComponentsHost proxy.',\n );\n const proxiedRes = await fetch(\n `${RC_PROTECTED_REMOTE_FETCH_PATHNAME}?url=${url}`,\n );\n if (proxiedRes.status === 200) {\n return proxiedRes;\n } else {\n logError(\n 'FetchRemoteComponent',\n `Could not proxy remote: [response status ${\n proxiedRes.status\n }] ${await proxiedRes.text()}`,\n );\n }\n }\n\n throw error;\n }\n}\n","/**\n * Proxy utilities for host applications that consume remote components.\n *\n * Hosts do NOT handle CORS - that's the remote's responsibility.\n * Hosts only handle protected fetch proxying.\n */\n\nimport { remoteFetchHeaders } from '#internal/shared/ssr/fetch-headers';\nimport { RC_PROTECTED_REMOTE_FETCH_PATHNAME } from '#internal/shared/ssr/fetch-with-protected-rc-fallback';\n\nexport interface HostProxyOptions {\n /**\n * List of allowed URL patterns (as regex strings) that can be proxied.\n * These patterns are combined with REMOTE_COMPONENTS_ALLOWED_PROXY_URLS env var if both are set.\n * If neither is set, all URLs are blocked.\n */\n allowedProxyUrls?: string[];\n}\n\n/**\n * Validates if a URL is allowed to be proxied based on the allowed patterns.\n *\n * @param targetUrl - The URL to validate\n * @param options - Host proxy configuration options\n * @returns true if the URL is allowed, false otherwise\n */\nfunction isUrlAllowed(targetUrl: string, options?: HostProxyOptions): boolean {\n const envPatterns = process.env.REMOTE_COMPONENTS_ALLOWED_PROXY_URLS?.split(\n ',',\n ).map((p) => p.trim());\n const optionPatterns = options?.allowedProxyUrls;\n\n // Combine both sources if both are specified\n const allowedPatterns = [...(optionPatterns || []), ...(envPatterns || [])];\n\n if (allowedPatterns.length === 0) {\n return false;\n }\n\n // Check if the URL matches any of the allowed patterns\n return allowedPatterns.some((pattern) => {\n try {\n const regex = new RegExp(pattern);\n return regex.test(targetUrl);\n } catch (error) {\n console.error(\n `Invalid regex pattern in allowedProxyUrls: ${pattern}`,\n error,\n );\n return false;\n }\n });\n}\n\n/**\n * Handles protected remote component fetch requests by proxying them with\n * authentication headers. This is needed for accessing Vercel-protected remote\n * component deployments from client-side code.\n *\n * @param requestUrl - The full request URL\n * @param options - Host proxy configuration options\n * @returns Response object if this is a protected fetch request, or null if not\n */\nexport async function handleProtectedRemoteFetchRequest(\n requestUrl: string,\n options?: HostProxyOptions,\n): Promise<Response | null> {\n const url = new URL(requestUrl, 'https://fallback.com');\n\n if (url.pathname !== RC_PROTECTED_REMOTE_FETCH_PATHNAME) {\n return null;\n }\n\n const targetUrl = url.searchParams.get('url');\n if (!targetUrl) {\n return new Response('Bad request, missing url query param', {\n status: 400,\n });\n }\n\n // Validate URL against allowed patterns to prevent SSRF attacks\n if (!isUrlAllowed(targetUrl, options)) {\n return new Response(\n `Forbidden: remote component URL ${url} does not match any allowedProxyUrls or REMOTE_COMPONENTS_ALLOWED_PROXY_URLS in withRemoteComponentsHost.`,\n {\n status: 403,\n },\n );\n }\n\n // Fetch the remote resource\n const response = await fetch(targetUrl, { headers: remoteFetchHeaders() });\n\n // Create new headers without content-encoding to avoid decoding errors\n // (Node.js fetch auto-decodes but keeps the header, causing browser issues)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nexport { RC_PROTECTED_REMOTE_FETCH_PATHNAME };\n"],"mappings":"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","names":["import_server"]}
1
+ {"version":3,"sources":["../../src/next/proxy/index.ts","../../src/next/proxy/withRemoteComponents.ts","../../src/shared/remote/proxy.ts","../../src/next/proxy/withRemoteComponentsHost.ts","../../src/shared/ssr/fetch-headers.ts","../../src/shared/utils/logger.ts","../../src/shared/ssr/fetch-with-protected-rc-fallback.ts","../../src/shared/host/proxy.ts"],"sourcesContent":["export {\n type RemoteComponentProxyOptions,\n withRemoteComponents,\n} from './withRemoteComponents';\nexport { withRemoteComponentsHost } from './withRemoteComponentsHost';\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n getCorsHeaders,\n handleCorsPreflightRequest,\n type RemoteComponentProxyOptions,\n} from '#internal/shared/remote/proxy';\n\n/**\n * This proxy is used to handle CORS and other remote component related tasks.\n * It can be used to wrap a Next.js proxy handler function to add CORS headers and handle preflight requests.\n *\n * @param proxy - The Next.js proxy handler function to wrap.\n * @param options - Optional configuration for handling remote components.\n * @returns A Next.js proxy handler function that handles CORS and preflight requests\n */\nexport function withRemoteComponents(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: RemoteComponentProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a CORS preflight request\n const preflightResponse = handleCorsPreflightRequest(\n request.method,\n request.headers,\n options?.cors,\n );\n\n if (preflightResponse) {\n return preflightResponse;\n }\n\n // For all other requests, continue and attach CORS headers\n const response =\n typeof proxy === 'function' ? await proxy(request) : NextResponse.next();\n\n if (options?.cors !== false) {\n const corsHeaders = getCorsHeaders(options?.cors, request.headers);\n Object.entries(corsHeaders).forEach(([k, v]) =>\n response.headers.set(k, v),\n );\n }\n\n return response;\n };\n}\n\nexport type { RemoteComponentProxyOptions };\n","/**\n * Proxy utilities for remote applications that expose components to hosts.\n */\n\nimport type { IncomingHttpHeaders } from 'node:http';\n\nexport interface RemoteComponentProxyOptions {\n cors?:\n | {\n origin?: string | string[];\n method?: string | string[];\n headers?: string | string[];\n credentials?: boolean;\n maxAge?: string;\n }\n | false;\n}\n\n/**\n * Gets a header value from either a Headers object or a plain object.\n */\nexport function getHeader(\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n name: string,\n): string | undefined {\n if (headers instanceof Headers) {\n return headers.get(name) ?? undefined;\n }\n const value = headers[name];\n // IncomingHttpHeaders can have string | string[] | undefined\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Computes CORS headers based on the provided options and request headers.\n *\n * @param options - CORS configuration options\n * @param requestHeaders - Headers from the incoming request (can be a Headers object or a plain object)\n * @returns Object containing CORS headers to be added to the response\n */\nexport function getCorsHeaders(\n options: RemoteComponentProxyOptions['cors'],\n requestHeaders: Record<string, string> | Headers | IncomingHttpHeaders,\n): Record<string, string> {\n if (options === false) {\n return {};\n }\n\n const originHeader = getHeader(requestHeaders, 'origin');\n const refererHeader = getHeader(requestHeaders, 'referer');\n const origin =\n originHeader ?? (refererHeader ? new URL(refererHeader).origin : '*');\n\n const ALLOWED_ORIGINS = (\n process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS ||\n (Array.isArray(options?.origin)\n ? options.origin.join(',')\n : options?.origin) ||\n '*'\n )\n .split(',')\n .map((allowedOrigin) => allowedOrigin.trim());\n\n const isAllowed =\n ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);\n\n const allowedHeaders =\n process.env.REMOTE_COMPONENTS_ALLOW_HEADERS ||\n (Array.isArray(options?.headers)\n ? options.headers.map((h) => h.trim()).join(',')\n : options?.headers) ||\n getHeader(requestHeaders, 'access-control-request-headers');\n\n const CORS_HEADERS = (\n isAllowed\n ? {\n 'Access-Control-Allow-Origin': origin,\n 'Access-Control-Allow-Methods':\n process.env.REMOTE_COMPONENTS_ALLOW_METHODS ||\n (Array.isArray(options?.method)\n ? options.method.map((m) => m.trim()).join(',')\n : options?.method) ||\n 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS',\n ...(allowedHeaders\n ? { 'Access-Control-Allow-Headers': allowedHeaders }\n : {}),\n ...(process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS ||\n options?.credentials\n ? {\n 'Access-Control-Allow-Credentials':\n process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || 'true',\n }\n : {}),\n 'Access-Control-Max-Age': options?.maxAge || '600',\n Vary: 'Origin',\n }\n : {}\n ) as Record<string, string>;\n\n return CORS_HEADERS;\n}\n\n/**\n * Handles CORS preflight OPTIONS requests.\n *\n * @param method - The HTTP method of the incoming request\n * @param headers - Headers from the incoming request (can be a Headers object or a plain object)\n * @param options - CORS configuration options\n * @returns Response object for the preflight request, or null if not a preflight\n */\nexport function handleCorsPreflightRequest(\n method: string,\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n options?: RemoteComponentProxyOptions['cors'],\n): Response | null {\n if (method !== 'OPTIONS' || options === false) {\n return null;\n }\n\n const corsHeaders = getCorsHeaders(options, headers);\n\n return new Response(undefined, {\n status: 200,\n headers: corsHeaders,\n });\n}\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n type HostProxyOptions,\n handleProtectedRemoteFetchRequest,\n} from '#internal/shared/host/proxy';\n\n/**\n * Only needed when accessing a remote component in a preview environment that\n * is protected with vercel deployment protection. If the remote component fetch\n * errors, it will attempt to fetch via this proxy which adds the protection\n * bypass header to the request.\n *\n * To use this proxy, the path `/rc-fetch-protected-remote` must be added to the\n * proxy/middleware matchers.\n *\n * @param proxy - Optional Next.js middleware function to run after checking for protected remote fetch\n * @param options - Host proxy configuration options for SSRF protection\n */\nexport function withRemoteComponentsHost(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: HostProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a protected remote fetch request\n const protectedFetchResponse = await handleProtectedRemoteFetchRequest(\n request.url,\n options,\n );\n\n if (protectedFetchResponse) {\n return protectedFetchResponse;\n }\n\n return typeof proxy === 'function'\n ? await proxy(request)\n : NextResponse.next();\n };\n}\n\nexport type { HostProxyOptions };\n","/**\n * The headers to use when fetching the remote component.\n */\nexport function remoteFetchHeaders() {\n return {\n /**\n * Authenticates deployment protection for the remote. Needed for SSR and SSG clients.\n * If the remote component uses vercel deployment protection, ensure the host and remote vercel\n * projects share a common automation bypass secret, and the shared secret is used as the\n * VERCEL_AUTOMATION_BYPASS_SECRET env var in the host project.\n */\n ...(typeof process === 'object' &&\n typeof process.env === 'object' &&\n typeof process.env.VERCEL_AUTOMATION_BYPASS_SECRET === 'string'\n ? {\n 'x-vercel-protection-bypass':\n process.env.VERCEL_AUTOMATION_BYPASS_SECRET,\n }\n : {}),\n Accept: 'text/html',\n };\n}\n","import { RemoteComponentsError } from '#internal/shared/error';\n\ntype LogLocation =\n | 'ChunkLoader'\n | 'ComponentLoader'\n | 'SharedModules'\n | 'WebpackAdapter'\n | 'TurbopackModule'\n | 'StaticLoader'\n | 'Polyfill'\n | 'HtmlRemote'\n | 'HtmlHost'\n | 'Config'\n | 'NextAppRouter'\n | 'NextAppRouterCompat'\n | 'FetchRemoteComponent';\n\nconst PREFIX = 'remote-components';\nconst DEBUG =\n typeof window !== 'undefined' && localStorage.getItem('RC_DEBUG') === 'true';\n\nexport function logDebug(location: LogLocation, message: string) {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.debug(`[${PREFIX}:${location}]: ${message}`);\n }\n}\n\nexport function logInfo(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.info(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logWarn(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.warn(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logError(\n location: LogLocation,\n message: string,\n cause?: unknown,\n) {\n // eslint-disable-next-line no-console\n console.error(\n new RemoteComponentsError(`[${PREFIX}:${location}]: ${message}`, {\n cause,\n }),\n );\n}\n","import { isAbortError } from '#internal/shared/utils/abort';\nimport { logError, logInfo } from '#internal/shared/utils/logger';\n\nexport const RC_PROTECTED_REMOTE_FETCH_PATHNAME = '/rc-fetch-protected-remote';\n\n/**\n * When a vercel host preview fetches a vercel protected remote preview on the\n * client, the request will reject as it's not possible to authenticate for the\n * protected deployment in a client side fetch - cookies cannot be shared across\n * domains. To enable previews, this request is proxied via the host where the\n * process.env.VERCEL_AUTOMATION_BYPASS_SECRET will be added.\n *\n * @param url - The URL to fetch\n * @param init - Fetch init options (should include signal for abort support)\n */\nexport async function fetchWithProtectedRcFallback(\n url: URL | string,\n init?: RequestInit,\n): Promise<Response> {\n try {\n const res = await fetch(url, init);\n return res;\n } catch (error) {\n // Re-throw AbortError immediately - don't try fallback for cancelled requests\n if (isAbortError(error)) {\n throw error;\n }\n\n if (\n typeof document === 'object' &&\n typeof document.location === 'object' &&\n document.location.origin !== new URL(url).origin\n ) {\n logInfo(\n 'FetchRemoteComponent',\n 'Request failed due to CORS, attempting to fetch it via the withRemoteComponentsHost proxy.',\n );\n // Pass signal to proxy fetch as well so it can be cancelled\n const proxiedRes = await fetch(\n `${RC_PROTECTED_REMOTE_FETCH_PATHNAME}?url=${url}`,\n init?.signal ? { signal: init.signal } : undefined,\n );\n if (proxiedRes.status === 200) {\n return proxiedRes;\n } else {\n logError(\n 'FetchRemoteComponent',\n `Could not proxy remote: [response status ${\n proxiedRes.status\n }] ${await proxiedRes.text()}`,\n );\n }\n }\n\n throw error;\n }\n}\n","/**\n * Proxy utilities for host applications that consume remote components.\n *\n * Hosts do NOT handle CORS - that's the remote's responsibility.\n * Hosts only handle protected fetch proxying.\n */\n\nimport { remoteFetchHeaders } from '#internal/shared/ssr/fetch-headers';\nimport { RC_PROTECTED_REMOTE_FETCH_PATHNAME } from '#internal/shared/ssr/fetch-with-protected-rc-fallback';\n\nexport interface HostProxyOptions {\n /**\n * List of allowed URL patterns (as regex strings) that can be proxied.\n * These patterns are combined with REMOTE_COMPONENTS_ALLOWED_PROXY_URLS env var if both are set.\n * If neither is set, all URLs are blocked.\n */\n allowedProxyUrls?: string[];\n}\n\n/**\n * Validates if a URL is allowed to be proxied based on the allowed patterns.\n *\n * @param targetUrl - The URL to validate\n * @param options - Host proxy configuration options\n * @returns true if the URL is allowed, false otherwise\n */\nfunction isUrlAllowed(targetUrl: string, options?: HostProxyOptions): boolean {\n const envPatterns = process.env.REMOTE_COMPONENTS_ALLOWED_PROXY_URLS?.split(\n ',',\n ).map((p) => p.trim());\n const optionPatterns = options?.allowedProxyUrls;\n\n // Combine both sources if both are specified\n const allowedPatterns = [...(optionPatterns || []), ...(envPatterns || [])];\n\n if (allowedPatterns.length === 0) {\n return false;\n }\n\n // Check if the URL matches any of the allowed patterns\n return allowedPatterns.some((pattern) => {\n try {\n const regex = new RegExp(pattern);\n return regex.test(targetUrl);\n } catch (error) {\n console.error(\n `Invalid regex pattern in allowedProxyUrls: ${pattern}`,\n error,\n );\n return false;\n }\n });\n}\n\n/**\n * Handles protected remote component fetch requests by proxying them with\n * authentication headers. This is needed for accessing Vercel-protected remote\n * component deployments from client-side code.\n *\n * @param requestUrl - The full request URL\n * @param options - Host proxy configuration options\n * @returns Response object if this is a protected fetch request, or null if not\n */\nexport async function handleProtectedRemoteFetchRequest(\n requestUrl: string,\n options?: HostProxyOptions,\n): Promise<Response | null> {\n const url = new URL(requestUrl, 'https://fallback.com');\n\n if (url.pathname !== RC_PROTECTED_REMOTE_FETCH_PATHNAME) {\n return null;\n }\n\n const targetUrl = url.searchParams.get('url');\n if (!targetUrl) {\n return new Response('Bad request, missing url query param', {\n status: 400,\n });\n }\n\n // Validate URL against allowed patterns to prevent SSRF attacks\n if (!isUrlAllowed(targetUrl, options)) {\n return new Response(\n `Forbidden: remote component URL ${url} does not match any allowedProxyUrls or REMOTE_COMPONENTS_ALLOWED_PROXY_URLS in withRemoteComponentsHost.`,\n {\n status: 403,\n },\n );\n }\n\n // Fetch the remote resource\n const response = await fetch(targetUrl, { headers: remoteFetchHeaders() });\n\n // Create new headers without content-encoding to avoid decoding errors\n // (Node.js fetch auto-decodes but keeps the header, causing browser issues)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nexport { RC_PROTECTED_REMOTE_FETCH_PATHNAME };\n"],"mappings":"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","names":["import_server"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/proxy/withRemoteComponents.ts","../../src/shared/remote/proxy.ts","../../src/next/proxy/withRemoteComponentsHost.ts","../../src/shared/ssr/fetch-headers.ts","../../src/shared/utils/logger.ts","../../src/shared/ssr/fetch-with-protected-rc-fallback.ts","../../src/shared/host/proxy.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\nimport {\n getCorsHeaders,\n handleCorsPreflightRequest,\n type RemoteComponentProxyOptions,\n} from '#internal/shared/remote/proxy';\n\n/**\n * This proxy is used to handle CORS and other remote component related tasks.\n * It can be used to wrap a Next.js proxy handler function to add CORS headers and handle preflight requests.\n *\n * @param proxy - The Next.js proxy handler function to wrap.\n * @param options - Optional configuration for handling remote components.\n * @returns A Next.js proxy handler function that handles CORS and preflight requests\n */\nexport function withRemoteComponents(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: RemoteComponentProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a CORS preflight request\n const preflightResponse = handleCorsPreflightRequest(\n request.method,\n request.headers,\n options?.cors,\n );\n\n if (preflightResponse) {\n return preflightResponse;\n }\n\n // For all other requests, continue and attach CORS headers\n const response =\n typeof proxy === 'function' ? await proxy(request) : NextResponse.next();\n\n if (options?.cors !== false) {\n const corsHeaders = getCorsHeaders(options?.cors, request.headers);\n Object.entries(corsHeaders).forEach(([k, v]) =>\n response.headers.set(k, v),\n );\n }\n\n return response;\n };\n}\n\nexport type { RemoteComponentProxyOptions };\n","/**\n * Proxy utilities for remote applications that expose components to hosts.\n */\n\nimport type { IncomingHttpHeaders } from 'node:http';\n\nexport interface RemoteComponentProxyOptions {\n cors?:\n | {\n origin?: string | string[];\n method?: string | string[];\n headers?: string | string[];\n credentials?: boolean;\n maxAge?: string;\n }\n | false;\n}\n\n/**\n * Gets a header value from either a Headers object or a plain object.\n */\nexport function getHeader(\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n name: string,\n): string | undefined {\n if (headers instanceof Headers) {\n return headers.get(name) ?? undefined;\n }\n const value = headers[name];\n // IncomingHttpHeaders can have string | string[] | undefined\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Computes CORS headers based on the provided options and request headers.\n *\n * @param options - CORS configuration options\n * @param requestHeaders - Headers from the incoming request (can be a Headers object or a plain object)\n * @returns Object containing CORS headers to be added to the response\n */\nexport function getCorsHeaders(\n options: RemoteComponentProxyOptions['cors'],\n requestHeaders: Record<string, string> | Headers | IncomingHttpHeaders,\n): Record<string, string> {\n if (options === false) {\n return {};\n }\n\n const originHeader = getHeader(requestHeaders, 'origin');\n const refererHeader = getHeader(requestHeaders, 'referer');\n const origin =\n originHeader ?? (refererHeader ? new URL(refererHeader).origin : '*');\n\n const ALLOWED_ORIGINS = (\n process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS ||\n (Array.isArray(options?.origin)\n ? options.origin.join(',')\n : options?.origin) ||\n '*'\n )\n .split(',')\n .map((allowedOrigin) => allowedOrigin.trim());\n\n const isAllowed =\n ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);\n\n const allowedHeaders =\n process.env.REMOTE_COMPONENTS_ALLOW_HEADERS ||\n (Array.isArray(options?.headers)\n ? options.headers.map((h) => h.trim()).join(',')\n : options?.headers) ||\n getHeader(requestHeaders, 'access-control-request-headers');\n\n const CORS_HEADERS = (\n isAllowed\n ? {\n 'Access-Control-Allow-Origin': origin,\n 'Access-Control-Allow-Methods':\n process.env.REMOTE_COMPONENTS_ALLOW_METHODS ||\n (Array.isArray(options?.method)\n ? options.method.map((m) => m.trim()).join(',')\n : options?.method) ||\n 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS',\n ...(allowedHeaders\n ? { 'Access-Control-Allow-Headers': allowedHeaders }\n : {}),\n ...(process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS ||\n options?.credentials\n ? {\n 'Access-Control-Allow-Credentials':\n process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || 'true',\n }\n : {}),\n 'Access-Control-Max-Age': options?.maxAge || '600',\n Vary: 'Origin',\n }\n : {}\n ) as Record<string, string>;\n\n return CORS_HEADERS;\n}\n\n/**\n * Handles CORS preflight OPTIONS requests.\n *\n * @param method - The HTTP method of the incoming request\n * @param headers - Headers from the incoming request (can be a Headers object or a plain object)\n * @param options - CORS configuration options\n * @returns Response object for the preflight request, or null if not a preflight\n */\nexport function handleCorsPreflightRequest(\n method: string,\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n options?: RemoteComponentProxyOptions['cors'],\n): Response | null {\n if (method !== 'OPTIONS' || options === false) {\n return null;\n }\n\n const corsHeaders = getCorsHeaders(options, headers);\n\n return new Response(undefined, {\n status: 200,\n headers: corsHeaders,\n });\n}\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n type HostProxyOptions,\n handleProtectedRemoteFetchRequest,\n} from '#internal/shared/host/proxy';\n\n/**\n * Only needed when accessing a remote component in a preview environment that\n * is protected with vercel deployment protection. If the remote component fetch\n * errors, it will attempt to fetch via this proxy which adds the protection\n * bypass header to the request.\n *\n * To use this proxy, the path `/rc-fetch-protected-remote` must be added to the\n * proxy/middleware matchers.\n *\n * @param proxy - Optional Next.js middleware function to run after checking for protected remote fetch\n * @param options - Host proxy configuration options for SSRF protection\n */\nexport function withRemoteComponentsHost(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: HostProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a protected remote fetch request\n const protectedFetchResponse = await handleProtectedRemoteFetchRequest(\n request.url,\n options,\n );\n\n if (protectedFetchResponse) {\n return protectedFetchResponse;\n }\n\n return typeof proxy === 'function'\n ? await proxy(request)\n : NextResponse.next();\n };\n}\n\nexport type { HostProxyOptions };\n","/**\n * The headers to use when fetching the remote component.\n */\nexport function remoteFetchHeaders() {\n return {\n /**\n * Authenticates deployment protection for the remote. Needed for SSR and SSG clients.\n * If the remote component uses vercel deployment protection, ensure the host and remote vercel\n * projects share a common automation bypass secret, and the shared secret is used as the\n * VERCEL_AUTOMATION_BYPASS_SECRET env var in the host project.\n */\n ...(typeof process === 'object' &&\n typeof process.env === 'object' &&\n typeof process.env.VERCEL_AUTOMATION_BYPASS_SECRET === 'string'\n ? {\n 'x-vercel-protection-bypass':\n process.env.VERCEL_AUTOMATION_BYPASS_SECRET,\n }\n : {}),\n Accept: 'text/html',\n };\n}\n","import { RemoteComponentsError } from '#internal/shared/error';\n\ntype LogLocation =\n | 'ChunkLoader'\n | 'ComponentLoader'\n | 'SharedModules'\n | 'WebpackAdapter'\n | 'TurbopackModule'\n | 'StaticLoader'\n | 'Polyfill'\n | 'HtmlRemote'\n | 'HtmlHost'\n | 'Config'\n | 'NextAppRouter'\n | 'NextAppRouterCompat'\n | 'FetchRemoteComponent';\n\nconst PREFIX = 'remote-components';\nconst DEBUG =\n typeof window !== 'undefined' && localStorage.getItem('RC_DEBUG') === 'true';\n\nexport function logDebug(location: LogLocation, message: string) {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.debug(`[${PREFIX}:${location}]: ${message}`);\n }\n}\n\nexport function logInfo(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.info(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logWarn(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.warn(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logError(\n location: LogLocation,\n message: string,\n cause?: unknown,\n) {\n // eslint-disable-next-line no-console\n console.error(\n new RemoteComponentsError(`[${PREFIX}:${location}]: ${message}`, {\n cause,\n }),\n );\n}\n","import { logError, logInfo } from '#internal/shared/utils/logger';\n\nexport const RC_PROTECTED_REMOTE_FETCH_PATHNAME = '/rc-fetch-protected-remote';\n\n/**\n * When a vercel host preview fetches a vercel protected remote preview on the\n * client, the request will reject as it's not possible to authenticate for the\n * protected deployment in a client side fetch - cookies cannot be shared across\n * domains. To enable previews, this request is proxied via the host where the\n * process.env.VERCEL_AUTOMATION_BYPASS_SECRET will be added.\n */\nexport async function fetchWithProtectedRcFallback(\n url: URL | string,\n init?: RequestInit,\n): Promise<Response> {\n try {\n const res = await fetch(url, init);\n return res;\n } catch (error) {\n if (\n typeof document === 'object' &&\n typeof document.location === 'object' &&\n document.location.origin !== new URL(url).origin\n ) {\n logInfo(\n 'FetchRemoteComponent',\n 'Request failed due to CORS, attempting to fetch it via the withRemoteComponentsHost proxy.',\n );\n const proxiedRes = await fetch(\n `${RC_PROTECTED_REMOTE_FETCH_PATHNAME}?url=${url}`,\n );\n if (proxiedRes.status === 200) {\n return proxiedRes;\n } else {\n logError(\n 'FetchRemoteComponent',\n `Could not proxy remote: [response status ${\n proxiedRes.status\n }] ${await proxiedRes.text()}`,\n );\n }\n }\n\n throw error;\n }\n}\n","/**\n * Proxy utilities for host applications that consume remote components.\n *\n * Hosts do NOT handle CORS - that's the remote's responsibility.\n * Hosts only handle protected fetch proxying.\n */\n\nimport { remoteFetchHeaders } from '#internal/shared/ssr/fetch-headers';\nimport { RC_PROTECTED_REMOTE_FETCH_PATHNAME } from '#internal/shared/ssr/fetch-with-protected-rc-fallback';\n\nexport interface HostProxyOptions {\n /**\n * List of allowed URL patterns (as regex strings) that can be proxied.\n * These patterns are combined with REMOTE_COMPONENTS_ALLOWED_PROXY_URLS env var if both are set.\n * If neither is set, all URLs are blocked.\n */\n allowedProxyUrls?: string[];\n}\n\n/**\n * Validates if a URL is allowed to be proxied based on the allowed patterns.\n *\n * @param targetUrl - The URL to validate\n * @param options - Host proxy configuration options\n * @returns true if the URL is allowed, false otherwise\n */\nfunction isUrlAllowed(targetUrl: string, options?: HostProxyOptions): boolean {\n const envPatterns = process.env.REMOTE_COMPONENTS_ALLOWED_PROXY_URLS?.split(\n ',',\n ).map((p) => p.trim());\n const optionPatterns = options?.allowedProxyUrls;\n\n // Combine both sources if both are specified\n const allowedPatterns = [...(optionPatterns || []), ...(envPatterns || [])];\n\n if (allowedPatterns.length === 0) {\n return false;\n }\n\n // Check if the URL matches any of the allowed patterns\n return allowedPatterns.some((pattern) => {\n try {\n const regex = new RegExp(pattern);\n return regex.test(targetUrl);\n } catch (error) {\n console.error(\n `Invalid regex pattern in allowedProxyUrls: ${pattern}`,\n error,\n );\n return false;\n }\n });\n}\n\n/**\n * Handles protected remote component fetch requests by proxying them with\n * authentication headers. This is needed for accessing Vercel-protected remote\n * component deployments from client-side code.\n *\n * @param requestUrl - The full request URL\n * @param options - Host proxy configuration options\n * @returns Response object if this is a protected fetch request, or null if not\n */\nexport async function handleProtectedRemoteFetchRequest(\n requestUrl: string,\n options?: HostProxyOptions,\n): Promise<Response | null> {\n const url = new URL(requestUrl, 'https://fallback.com');\n\n if (url.pathname !== RC_PROTECTED_REMOTE_FETCH_PATHNAME) {\n return null;\n }\n\n const targetUrl = url.searchParams.get('url');\n if (!targetUrl) {\n return new Response('Bad request, missing url query param', {\n status: 400,\n });\n }\n\n // Validate URL against allowed patterns to prevent SSRF attacks\n if (!isUrlAllowed(targetUrl, options)) {\n return new Response(\n `Forbidden: remote component URL ${url} does not match any allowedProxyUrls or REMOTE_COMPONENTS_ALLOWED_PROXY_URLS in withRemoteComponentsHost.`,\n {\n status: 403,\n },\n );\n }\n\n // Fetch the remote resource\n const response = await fetch(targetUrl, { headers: remoteFetchHeaders() });\n\n // Create new headers without content-encoding to avoid decoding errors\n // (Node.js fetch auto-decodes but keeps the header, causing browser issues)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nexport { RC_PROTECTED_REMOTE_FETCH_PATHNAME };\n"],"mappings":"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","names":["NextResponse","NextResponse"]}
1
+ {"version":3,"sources":["../../src/next/proxy/withRemoteComponents.ts","../../src/shared/remote/proxy.ts","../../src/next/proxy/withRemoteComponentsHost.ts","../../src/shared/ssr/fetch-headers.ts","../../src/shared/utils/logger.ts","../../src/shared/ssr/fetch-with-protected-rc-fallback.ts","../../src/shared/host/proxy.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\nimport {\n getCorsHeaders,\n handleCorsPreflightRequest,\n type RemoteComponentProxyOptions,\n} from '#internal/shared/remote/proxy';\n\n/**\n * This proxy is used to handle CORS and other remote component related tasks.\n * It can be used to wrap a Next.js proxy handler function to add CORS headers and handle preflight requests.\n *\n * @param proxy - The Next.js proxy handler function to wrap.\n * @param options - Optional configuration for handling remote components.\n * @returns A Next.js proxy handler function that handles CORS and preflight requests\n */\nexport function withRemoteComponents(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: RemoteComponentProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a CORS preflight request\n const preflightResponse = handleCorsPreflightRequest(\n request.method,\n request.headers,\n options?.cors,\n );\n\n if (preflightResponse) {\n return preflightResponse;\n }\n\n // For all other requests, continue and attach CORS headers\n const response =\n typeof proxy === 'function' ? await proxy(request) : NextResponse.next();\n\n if (options?.cors !== false) {\n const corsHeaders = getCorsHeaders(options?.cors, request.headers);\n Object.entries(corsHeaders).forEach(([k, v]) =>\n response.headers.set(k, v),\n );\n }\n\n return response;\n };\n}\n\nexport type { RemoteComponentProxyOptions };\n","/**\n * Proxy utilities for remote applications that expose components to hosts.\n */\n\nimport type { IncomingHttpHeaders } from 'node:http';\n\nexport interface RemoteComponentProxyOptions {\n cors?:\n | {\n origin?: string | string[];\n method?: string | string[];\n headers?: string | string[];\n credentials?: boolean;\n maxAge?: string;\n }\n | false;\n}\n\n/**\n * Gets a header value from either a Headers object or a plain object.\n */\nexport function getHeader(\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n name: string,\n): string | undefined {\n if (headers instanceof Headers) {\n return headers.get(name) ?? undefined;\n }\n const value = headers[name];\n // IncomingHttpHeaders can have string | string[] | undefined\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Computes CORS headers based on the provided options and request headers.\n *\n * @param options - CORS configuration options\n * @param requestHeaders - Headers from the incoming request (can be a Headers object or a plain object)\n * @returns Object containing CORS headers to be added to the response\n */\nexport function getCorsHeaders(\n options: RemoteComponentProxyOptions['cors'],\n requestHeaders: Record<string, string> | Headers | IncomingHttpHeaders,\n): Record<string, string> {\n if (options === false) {\n return {};\n }\n\n const originHeader = getHeader(requestHeaders, 'origin');\n const refererHeader = getHeader(requestHeaders, 'referer');\n const origin =\n originHeader ?? (refererHeader ? new URL(refererHeader).origin : '*');\n\n const ALLOWED_ORIGINS = (\n process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS ||\n (Array.isArray(options?.origin)\n ? options.origin.join(',')\n : options?.origin) ||\n '*'\n )\n .split(',')\n .map((allowedOrigin) => allowedOrigin.trim());\n\n const isAllowed =\n ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);\n\n const allowedHeaders =\n process.env.REMOTE_COMPONENTS_ALLOW_HEADERS ||\n (Array.isArray(options?.headers)\n ? options.headers.map((h) => h.trim()).join(',')\n : options?.headers) ||\n getHeader(requestHeaders, 'access-control-request-headers');\n\n const CORS_HEADERS = (\n isAllowed\n ? {\n 'Access-Control-Allow-Origin': origin,\n 'Access-Control-Allow-Methods':\n process.env.REMOTE_COMPONENTS_ALLOW_METHODS ||\n (Array.isArray(options?.method)\n ? options.method.map((m) => m.trim()).join(',')\n : options?.method) ||\n 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS',\n ...(allowedHeaders\n ? { 'Access-Control-Allow-Headers': allowedHeaders }\n : {}),\n ...(process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS ||\n options?.credentials\n ? {\n 'Access-Control-Allow-Credentials':\n process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || 'true',\n }\n : {}),\n 'Access-Control-Max-Age': options?.maxAge || '600',\n Vary: 'Origin',\n }\n : {}\n ) as Record<string, string>;\n\n return CORS_HEADERS;\n}\n\n/**\n * Handles CORS preflight OPTIONS requests.\n *\n * @param method - The HTTP method of the incoming request\n * @param headers - Headers from the incoming request (can be a Headers object or a plain object)\n * @param options - CORS configuration options\n * @returns Response object for the preflight request, or null if not a preflight\n */\nexport function handleCorsPreflightRequest(\n method: string,\n headers: Record<string, string> | Headers | IncomingHttpHeaders,\n options?: RemoteComponentProxyOptions['cors'],\n): Response | null {\n if (method !== 'OPTIONS' || options === false) {\n return null;\n }\n\n const corsHeaders = getCorsHeaders(options, headers);\n\n return new Response(undefined, {\n status: 200,\n headers: corsHeaders,\n });\n}\n","import { type NextRequest, NextResponse } from 'next/server';\nimport {\n type HostProxyOptions,\n handleProtectedRemoteFetchRequest,\n} from '#internal/shared/host/proxy';\n\n/**\n * Only needed when accessing a remote component in a preview environment that\n * is protected with vercel deployment protection. If the remote component fetch\n * errors, it will attempt to fetch via this proxy which adds the protection\n * bypass header to the request.\n *\n * To use this proxy, the path `/rc-fetch-protected-remote` must be added to the\n * proxy/middleware matchers.\n *\n * @param proxy - Optional Next.js middleware function to run after checking for protected remote fetch\n * @param options - Host proxy configuration options for SSRF protection\n */\nexport function withRemoteComponentsHost(\n proxy?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: HostProxyOptions,\n) {\n return async (request: NextRequest) => {\n // Check if this is a protected remote fetch request\n const protectedFetchResponse = await handleProtectedRemoteFetchRequest(\n request.url,\n options,\n );\n\n if (protectedFetchResponse) {\n return protectedFetchResponse;\n }\n\n return typeof proxy === 'function'\n ? await proxy(request)\n : NextResponse.next();\n };\n}\n\nexport type { HostProxyOptions };\n","/**\n * The headers to use when fetching the remote component.\n */\nexport function remoteFetchHeaders() {\n return {\n /**\n * Authenticates deployment protection for the remote. Needed for SSR and SSG clients.\n * If the remote component uses vercel deployment protection, ensure the host and remote vercel\n * projects share a common automation bypass secret, and the shared secret is used as the\n * VERCEL_AUTOMATION_BYPASS_SECRET env var in the host project.\n */\n ...(typeof process === 'object' &&\n typeof process.env === 'object' &&\n typeof process.env.VERCEL_AUTOMATION_BYPASS_SECRET === 'string'\n ? {\n 'x-vercel-protection-bypass':\n process.env.VERCEL_AUTOMATION_BYPASS_SECRET,\n }\n : {}),\n Accept: 'text/html',\n };\n}\n","import { RemoteComponentsError } from '#internal/shared/error';\n\ntype LogLocation =\n | 'ChunkLoader'\n | 'ComponentLoader'\n | 'SharedModules'\n | 'WebpackAdapter'\n | 'TurbopackModule'\n | 'StaticLoader'\n | 'Polyfill'\n | 'HtmlRemote'\n | 'HtmlHost'\n | 'Config'\n | 'NextAppRouter'\n | 'NextAppRouterCompat'\n | 'FetchRemoteComponent';\n\nconst PREFIX = 'remote-components';\nconst DEBUG =\n typeof window !== 'undefined' && localStorage.getItem('RC_DEBUG') === 'true';\n\nexport function logDebug(location: LogLocation, message: string) {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.debug(`[${PREFIX}:${location}]: ${message}`);\n }\n}\n\nexport function logInfo(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.info(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logWarn(location: LogLocation, message: string) {\n // eslint-disable-next-line no-console\n console.warn(`[${PREFIX}:${location}]: ${message}`);\n}\n\nexport function logError(\n location: LogLocation,\n message: string,\n cause?: unknown,\n) {\n // eslint-disable-next-line no-console\n console.error(\n new RemoteComponentsError(`[${PREFIX}:${location}]: ${message}`, {\n cause,\n }),\n );\n}\n","import { isAbortError } from '#internal/shared/utils/abort';\nimport { logError, logInfo } from '#internal/shared/utils/logger';\n\nexport const RC_PROTECTED_REMOTE_FETCH_PATHNAME = '/rc-fetch-protected-remote';\n\n/**\n * When a vercel host preview fetches a vercel protected remote preview on the\n * client, the request will reject as it's not possible to authenticate for the\n * protected deployment in a client side fetch - cookies cannot be shared across\n * domains. To enable previews, this request is proxied via the host where the\n * process.env.VERCEL_AUTOMATION_BYPASS_SECRET will be added.\n *\n * @param url - The URL to fetch\n * @param init - Fetch init options (should include signal for abort support)\n */\nexport async function fetchWithProtectedRcFallback(\n url: URL | string,\n init?: RequestInit,\n): Promise<Response> {\n try {\n const res = await fetch(url, init);\n return res;\n } catch (error) {\n // Re-throw AbortError immediately - don't try fallback for cancelled requests\n if (isAbortError(error)) {\n throw error;\n }\n\n if (\n typeof document === 'object' &&\n typeof document.location === 'object' &&\n document.location.origin !== new URL(url).origin\n ) {\n logInfo(\n 'FetchRemoteComponent',\n 'Request failed due to CORS, attempting to fetch it via the withRemoteComponentsHost proxy.',\n );\n // Pass signal to proxy fetch as well so it can be cancelled\n const proxiedRes = await fetch(\n `${RC_PROTECTED_REMOTE_FETCH_PATHNAME}?url=${url}`,\n init?.signal ? { signal: init.signal } : undefined,\n );\n if (proxiedRes.status === 200) {\n return proxiedRes;\n } else {\n logError(\n 'FetchRemoteComponent',\n `Could not proxy remote: [response status ${\n proxiedRes.status\n }] ${await proxiedRes.text()}`,\n );\n }\n }\n\n throw error;\n }\n}\n","/**\n * Proxy utilities for host applications that consume remote components.\n *\n * Hosts do NOT handle CORS - that's the remote's responsibility.\n * Hosts only handle protected fetch proxying.\n */\n\nimport { remoteFetchHeaders } from '#internal/shared/ssr/fetch-headers';\nimport { RC_PROTECTED_REMOTE_FETCH_PATHNAME } from '#internal/shared/ssr/fetch-with-protected-rc-fallback';\n\nexport interface HostProxyOptions {\n /**\n * List of allowed URL patterns (as regex strings) that can be proxied.\n * These patterns are combined with REMOTE_COMPONENTS_ALLOWED_PROXY_URLS env var if both are set.\n * If neither is set, all URLs are blocked.\n */\n allowedProxyUrls?: string[];\n}\n\n/**\n * Validates if a URL is allowed to be proxied based on the allowed patterns.\n *\n * @param targetUrl - The URL to validate\n * @param options - Host proxy configuration options\n * @returns true if the URL is allowed, false otherwise\n */\nfunction isUrlAllowed(targetUrl: string, options?: HostProxyOptions): boolean {\n const envPatterns = process.env.REMOTE_COMPONENTS_ALLOWED_PROXY_URLS?.split(\n ',',\n ).map((p) => p.trim());\n const optionPatterns = options?.allowedProxyUrls;\n\n // Combine both sources if both are specified\n const allowedPatterns = [...(optionPatterns || []), ...(envPatterns || [])];\n\n if (allowedPatterns.length === 0) {\n return false;\n }\n\n // Check if the URL matches any of the allowed patterns\n return allowedPatterns.some((pattern) => {\n try {\n const regex = new RegExp(pattern);\n return regex.test(targetUrl);\n } catch (error) {\n console.error(\n `Invalid regex pattern in allowedProxyUrls: ${pattern}`,\n error,\n );\n return false;\n }\n });\n}\n\n/**\n * Handles protected remote component fetch requests by proxying them with\n * authentication headers. This is needed for accessing Vercel-protected remote\n * component deployments from client-side code.\n *\n * @param requestUrl - The full request URL\n * @param options - Host proxy configuration options\n * @returns Response object if this is a protected fetch request, or null if not\n */\nexport async function handleProtectedRemoteFetchRequest(\n requestUrl: string,\n options?: HostProxyOptions,\n): Promise<Response | null> {\n const url = new URL(requestUrl, 'https://fallback.com');\n\n if (url.pathname !== RC_PROTECTED_REMOTE_FETCH_PATHNAME) {\n return null;\n }\n\n const targetUrl = url.searchParams.get('url');\n if (!targetUrl) {\n return new Response('Bad request, missing url query param', {\n status: 400,\n });\n }\n\n // Validate URL against allowed patterns to prevent SSRF attacks\n if (!isUrlAllowed(targetUrl, options)) {\n return new Response(\n `Forbidden: remote component URL ${url} does not match any allowedProxyUrls or REMOTE_COMPONENTS_ALLOWED_PROXY_URLS in withRemoteComponentsHost.`,\n {\n status: 403,\n },\n );\n }\n\n // Fetch the remote resource\n const response = await fetch(targetUrl, { headers: remoteFetchHeaders() });\n\n // Create new headers without content-encoding to avoid decoding errors\n // (Node.js fetch auto-decodes but keeps the header, causing browser issues)\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nexport { RC_PROTECTED_REMOTE_FETCH_PATHNAME };\n"],"mappings":"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","names":["NextResponse","NextResponse"]}
@@ -606,6 +606,18 @@ async function loadScripts(scripts) {
606
606
  );
607
607
  }
608
608
 
609
+ // src/shared/utils/abort.ts
610
+ function isAbortError(error) {
611
+ if (error instanceof DOMException && error.name === "AbortError") {
612
+ return true;
613
+ }
614
+ if (error !== null && typeof error === "object" && "name" in error && error.name === "AbortError" && "message" in error && typeof error.message === "string") {
615
+ const e = error;
616
+ return typeof e.code === "number" || e.constructor?.name === "DOMException";
617
+ }
618
+ return false;
619
+ }
620
+
609
621
  // src/shared/ssr/fetch-with-protected-rc-fallback.ts
610
622
  var RC_PROTECTED_REMOTE_FETCH_PATHNAME = "/rc-fetch-protected-remote";
611
623
  async function fetchWithProtectedRcFallback(url, init) {
@@ -613,13 +625,17 @@ async function fetchWithProtectedRcFallback(url, init) {
613
625
  const res = await fetch(url, init);
614
626
  return res;
615
627
  } catch (error) {
628
+ if (isAbortError(error)) {
629
+ throw error;
630
+ }
616
631
  if (typeof document === "object" && typeof document.location === "object" && document.location.origin !== new URL(url).origin) {
617
632
  logInfo(
618
633
  "FetchRemoteComponent",
619
634
  "Request failed due to CORS, attempting to fetch it via the withRemoteComponentsHost proxy."
620
635
  );
621
636
  const proxiedRes = await fetch(
622
- `${RC_PROTECTED_REMOTE_FETCH_PATHNAME}?url=${url}`
637
+ `${RC_PROTECTED_REMOTE_FETCH_PATHNAME}?url=${url}`,
638
+ init?.signal ? { signal: init.signal } : void 0
623
639
  );
624
640
  if (proxiedRes.status === 200) {
625
641
  return proxiedRes;
@@ -1598,17 +1614,27 @@ function remoteFetchHeaders() {
1598
1614
 
1599
1615
  // src/shared/ssr/fetch-with-hooks.ts
1600
1616
  async function fetchWithHooks(url, additionalInit, options = {}) {
1601
- const { onRequest, onResponse } = options;
1617
+ const {
1618
+ onRequest,
1619
+ onResponse,
1620
+ abortController = new AbortController()
1621
+ } = options;
1622
+ const signal = abortController.signal;
1623
+ const hookOptions = {
1624
+ signal,
1625
+ abort: (reason) => abortController.abort(reason)
1626
+ };
1602
1627
  const init = {
1603
1628
  method: "GET",
1604
1629
  headers: remoteFetchHeaders(),
1630
+ signal,
1605
1631
  ...additionalInit
1606
1632
  };
1607
- let res = await onRequest?.(url, init);
1633
+ let res = await onRequest?.(url, init, hookOptions);
1608
1634
  if (!res) {
1609
1635
  res = await fetchWithProtectedRcFallback(url, init);
1610
1636
  }
1611
- const transformedRes = await onResponse?.(url, res);
1637
+ const transformedRes = await onResponse?.(url, res, hookOptions);
1612
1638
  if (transformedRes) {
1613
1639
  res = transformedRes;
1614
1640
  }
@@ -1820,14 +1846,16 @@ function RemoteComponent({
1820
1846
  const fetchInit = {
1821
1847
  credentials
1822
1848
  };
1849
+ const abortController = new AbortController();
1823
1850
  const res = await fetchWithHooks(url, fetchInit, {
1824
1851
  onRequest,
1825
- onResponse
1852
+ onResponse,
1853
+ abortController
1826
1854
  });
1827
- if (!res.ok) {
1855
+ if (!res || !res.ok) {
1828
1856
  let error = failedToFetchRemoteComponentError(
1829
1857
  url.href,
1830
- res
1858
+ res ?? new Response(null, { status: 0 })
1831
1859
  );
1832
1860
  try {
1833
1861
  const body = await res.text();
@@ -1848,7 +1876,9 @@ function RemoteComponent({
1848
1876
  error.stack = errorStack;
1849
1877
  }
1850
1878
  }
1851
- } catch {
1879
+ } catch (parseError) {
1880
+ if (isAbortError(parseError))
1881
+ throw parseError;
1852
1882
  }
1853
1883
  throw error;
1854
1884
  }
@@ -2119,6 +2149,9 @@ function RemoteComponent({
2119
2149
  }
2120
2150
  }
2121
2151
  } catch (error) {
2152
+ if (isAbortError(error)) {
2153
+ return;
2154
+ }
2122
2155
  setRemoteComponent(error);
2123
2156
  onError?.(error);
2124
2157
  }