{"version":3,"file":"RemoteRendererBrowser.mjs","names":[],"sources":["../../src/RemoteRendererBrowser.tsx"],"sourcesContent":["\"use client\";\n\nimport { HostRenderErrorBoundary } from \"@/components/HostRenderErrorBoundary\";\nimport { useMergedComponents } from \"@/hooks/useMergedComponents\";\nimport {\n  toComponentUsageEvent,\n  type ComponentUsageHandler,\n} from \"@/lib/componentUsage\";\nimport { getFlowComponentStatus } from \"@mittwald/flow-react-components/internal\";\nimport { useControllableSuspenseTrigger } from \"@/hooks/useControllableSuspenseTrigger\";\nimport { useUpdateHostPathnameOnRemote } from \"@/hooks/useUpdateHostPathnameOnRemote\";\nimport type { RemoteComponentsMap } from \"@/lib/types\";\nimport {\n  connectRemoteIframeRef,\n  RemoteError,\n  type HostToRemoteConnection,\n  type NavigationState,\n  type RemoteExtBridgeConnectionApi,\n  type RemoteReadyEvent,\n} from \"@mittwald/flow-remote-core\";\nimport { refresh, usePromise } from \"@mittwald/react-use-promise\";\nimport {\n  RemoteReceiver,\n  RemoteRootRenderer,\n} from \"@mittwald/remote-dom-react/host\";\nimport {\n  type CSSProperties,\n  type FC,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { useLanguage, useTheme } from \"@mittwald/flow-react-components\";\nimport type { HostConfig } from \"@mittwald/ext-bridge\";\n\nexport interface RemoteRendererBrowserProps {\n  integrations?: RemoteComponentsMap<never>[];\n  src?: string;\n  timeoutMs?: number;\n  onNavigationStateChanged?: (state: NavigationState) => void;\n  onConnected?: (event: RemoteReadyEvent) => void;\n  onDeprecation?: (message: string) => void;\n  onComponentUsage?: ComponentUsageHandler;\n  hostPathname?: string;\n  extBridgeImplementation?: RemoteExtBridgeConnectionApi;\n  /** Internal use only */\n  __remoteReceiver?: RemoteReceiver;\n}\n\nconst hiddenIframeStyle: CSSProperties = {\n  visibility: \"hidden\",\n  height: 0,\n  width: 0,\n  border: \"none\",\n  position: \"absolute\",\n  marginLeft: \"-9999px\",\n};\n\nexport const RemoteRendererBrowser: FC<RemoteRendererBrowserProps> = (\n  props,\n) => {\n  const {\n    integrations = [],\n    __remoteReceiver: remoteReceiverFromProps,\n    ...connectedProps\n  } = props;\n\n  const remoteComponents = useMergedComponents(integrations);\n\n  // The two branches use disjoint sets of hooks, so the connection path lives\n  // in a dedicated component. This keeps each component's hook order stable\n  // (Rules of Hooks) while preserving the exact rendering of both paths.\n  // Note that the receiver path has no connection, so it reports no usage.\n  if (remoteReceiverFromProps) {\n    return (\n      <RemoteRootRenderer\n        components={remoteComponents}\n        receiver={remoteReceiverFromProps}\n      />\n    );\n  }\n\n  const { src } = connectedProps;\n  if (!src) {\n    throw new RemoteError(\"'src' prop is required\");\n  }\n\n  return (\n    <RemoteRendererConnected\n      {...connectedProps}\n      src={src}\n      remoteComponents={remoteComponents}\n    />\n  );\n};\n\ninterface RemoteRendererConnectedProps extends Omit<\n  RemoteRendererBrowserProps,\n  \"integrations\" | \"__remoteReceiver\" | \"src\"\n> {\n  src: string;\n  remoteComponents: ReturnType<typeof useMergedComponents>;\n}\n\nconst RemoteRendererConnected: FC<RemoteRendererConnectedProps> = (props) => {\n  const {\n    timeoutMs = 10_000,\n    src,\n    extBridgeImplementation,\n    onNavigationStateChanged,\n    onConnected,\n    onDeprecation,\n    onComponentUsage,\n    hostPathname,\n    remoteComponents,\n  } = props;\n\n  const language = useLanguage();\n  const theme = useTheme();\n\n  const renderPromise = useMemo(() => Promise.withResolvers<void>(), [src]);\n  const connectionPromise = useMemo(() => Promise.withResolvers<void>(), [src]);\n  const loadingPromise = useMemo(() => Promise.withResolvers<void>(), [src]);\n  const suspenseTrigger = useControllableSuspenseTrigger();\n\n  const [connectionSrc, setConnectionSrc] = useState<string | null>(null);\n  const connection = useRef<HostToRemoteConnection>(undefined);\n  const [remoteError, setRemoteError] = useState<string | undefined>();\n\n  const [receiver, rendererSubscriber] = useMemo(() => {\n    const remoteReceiver = new RemoteReceiver();\n    const controller = new AbortController();\n    remoteReceiver.subscribe(\n      { id: remoteReceiver.root.id },\n      () => renderPromise.resolve(),\n      { signal: controller.signal },\n    );\n    return [remoteReceiver, controller];\n  }, [src]);\n\n  useUpdateHostPathnameOnRemote(hostPathname, connection.current);\n\n  const hostConfig: HostConfig = {\n    language,\n    theme,\n  };\n\n  const connect = connectRemoteIframeRef({\n    connection: receiver.connection,\n    extBridgeImplementation,\n    hostConfig,\n    onReady: ({ connection: establishedConnection, remoteReadyEvent }) => {\n      establishedConnection.updateHostPathname(hostPathname);\n      onConnected?.(remoteReadyEvent);\n      connectionPromise.resolve();\n    },\n    onLoadingChanged: (isLoading) => {\n      if (isLoading) {\n        suspenseTrigger.start();\n      } else {\n        suspenseTrigger.stop();\n      }\n    },\n    onError: setRemoteError,\n    onNavigationStateChanged,\n    onDeprecation,\n    onEvent: (event) => {\n      if (event.event === \"ComponentRendered\") {\n        onComponentUsage?.(\n          toComponentUsageEvent(event, getFlowComponentStatus),\n        );\n      }\n    },\n  });\n\n  const timeoutPromise = (message: string) =>\n    new Promise((_, rej) => {\n      setTimeout(() => {\n        rej(new RemoteError(`${message}: Timeout reached`));\n      }, timeoutMs);\n    });\n\n  const overallLoading = () =>\n    Promise.all([\n      Promise.race([\n        loadingPromise.promise,\n        timeoutPromise(\"Remote URL could not be loaded\"),\n      ]),\n      Promise.race([\n        connectionPromise.promise,\n        timeoutPromise(\"Could not establish remote connection\"),\n      ]),\n      Promise.race([\n        renderPromise.promise,\n        timeoutPromise(\"Remote rendering failed\"),\n      ]),\n    ]);\n\n  const awaitLoadingPromise = connectionSrc === src;\n\n  const loaderResourceTag = `mittwald/remote-react-renderer/connect/${src}`;\n\n  // refresh on remount\n  useEffect(\n    () => () => {\n      refresh({\n        tag: loaderResourceTag,\n      });\n    },\n    [src],\n  );\n\n  usePromise(\n    async () => {\n      await overallLoading();\n      rendererSubscriber.abort();\n    },\n    // Disabled while erroring so the error is thrown below instead of the\n    // loader suspending indefinitely.\n    awaitLoadingPromise && !remoteError ? [] : null,\n    {\n      loaderId: src,\n      tags: [loaderResourceTag],\n    },\n  );\n\n  if (remoteError) {\n    throw new RemoteError(`Remote rendering failed: ${remoteError}`);\n  }\n\n  return (\n    <>\n      <HostRenderErrorBoundary\n        onNoComponentError={async (error) => {\n          await connection.current?.reportHostError(\n            error instanceof Error ? error.message : String(error),\n          );\n        }}\n      >\n        <RemoteRootRenderer components={remoteComponents} receiver={receiver} />\n      </HostRenderErrorBoundary>\n      <iframe\n        src={src}\n        ref={(ref) => {\n          connection.current = connect(ref);\n          setConnectionSrc(src);\n        }}\n        onLoad={() => loadingPromise.resolve()}\n        onError={() => loadingPromise.reject()}\n        style={hiddenIframeStyle}\n      />\n    </>\n  );\n};\n\nexport default RemoteRendererBrowser;\n"],"mappings":";;;;;;;;;;;;;;;;AAkDA,IAAM,oBAAmC;CACvC,YAAY;CACZ,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,UAAU;CACV,YAAY;AACd;AAEA,IAAa,yBACX,UACG;CACH,MAAM,EACJ,eAAe,CAAC,GAChB,kBAAkB,yBAClB,GAAG,mBACD;CAEJ,MAAM,mBAAmB,oBAAoB,YAAY;CAMzD,IAAI,yBACF,OACE,oBAAC,oBAAD;EACE,YAAY;EACZ,UAAU;CACX,CAAA;CAIL,MAAM,EAAE,QAAQ;CAChB,IAAI,CAAC,KACH,MAAM,IAAI,YAAY,wBAAwB;CAGhD,OACE,oBAAC,yBAAD;EACE,GAAI;EACC;EACa;CACnB,CAAA;AAEL;AAUA,IAAM,2BAA6D,UAAU;CAC3E,MAAM,EACJ,YAAY,KACZ,KACA,yBACA,0BACA,aACA,eACA,kBACA,cACA,qBACE;CAEJ,MAAM,WAAW,YAAY;CAC7B,MAAM,QAAQ,SAAS;CAEvB,MAAM,gBAAgB,cAAc,QAAQ,cAAoB,GAAG,CAAC,GAAG,CAAC;CACxE,MAAM,oBAAoB,cAAc,QAAQ,cAAoB,GAAG,CAAC,GAAG,CAAC;CAC5E,MAAM,iBAAiB,cAAc,QAAQ,cAAoB,GAAG,CAAC,GAAG,CAAC;CACzE,MAAM,kBAAkB,+BAA+B;CAEvD,MAAM,CAAC,eAAe,oBAAoB,SAAwB,IAAI;CACtE,MAAM,aAAa,OAA+B,KAAA,CAAS;CAC3D,MAAM,CAAC,aAAa,kBAAkB,SAA6B;CAEnE,MAAM,CAAC,UAAU,sBAAsB,cAAc;EACnD,MAAM,iBAAiB,IAAI,eAAe;EAC1C,MAAM,aAAa,IAAI,gBAAgB;EACvC,eAAe,UACb,EAAE,IAAI,eAAe,KAAK,GAAG,SACvB,cAAc,QAAQ,GAC5B,EAAE,QAAQ,WAAW,OAAO,CAC9B;EACA,OAAO,CAAC,gBAAgB,UAAU;CACpC,GAAG,CAAC,GAAG,CAAC;CAER,8BAA8B,cAAc,WAAW,OAAO;CAE9D,MAAM,aAAyB;EAC7B;EACA;CACF;CAEA,MAAM,UAAU,uBAAuB;EACrC,YAAY,SAAS;EACrB;EACA;EACA,UAAU,EAAE,YAAY,uBAAuB,uBAAuB;GACpE,sBAAsB,mBAAmB,YAAY;GACrD,cAAc,gBAAgB;GAC9B,kBAAkB,QAAQ;EAC5B;EACA,mBAAmB,cAAc;GAC/B,IAAI,WACF,gBAAgB,MAAM;QAEtB,gBAAgB,KAAK;EAEzB;EACA,SAAS;EACT;EACA;EACA,UAAU,UAAU;GAClB,IAAI,MAAM,UAAU,qBAClB,mBACE,sBAAsB,OAAO,sBAAsB,CACrD;EAEJ;CACF,CAAC;CAED,MAAM,kBAAkB,YACtB,IAAI,SAAS,GAAG,QAAQ;EACtB,iBAAiB;GACf,IAAI,IAAI,YAAY,GAAG,QAAQ,kBAAkB,CAAC;EACpD,GAAG,SAAS;CACd,CAAC;CAEH,MAAM,uBACJ,QAAQ,IAAI;EACV,QAAQ,KAAK,CACX,eAAe,SACf,eAAe,gCAAgC,CACjD,CAAC;EACD,QAAQ,KAAK,CACX,kBAAkB,SAClB,eAAe,uCAAuC,CACxD,CAAC;EACD,QAAQ,KAAK,CACX,cAAc,SACd,eAAe,yBAAyB,CAC1C,CAAC;CACH,CAAC;CAEH,MAAM,sBAAsB,kBAAkB;CAE9C,MAAM,oBAAoB,0CAA0C;CAGpE,sBACc;EACV,QAAQ,EACN,KAAK,kBACP,CAAC;CACH,GACA,CAAC,GAAG,CACN;CAEA,WACE,YAAY;EACV,MAAM,eAAe;EACrB,mBAAmB,MAAM;CAC3B,GAGA,uBAAuB,CAAC,cAAc,CAAC,IAAI,MAC3C;EACE,UAAU;EACV,MAAM,CAAC,iBAAiB;CAC1B,CACF;CAEA,IAAI,aACF,MAAM,IAAI,YAAY,4BAA4B,aAAa;CAGjE,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,yBAAD;EACE,oBAAoB,OAAO,UAAU;GACnC,MAAM,WAAW,SAAS,gBACxB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CACvD;EACF;EAEA,UAAA,oBAAC,oBAAD;GAAoB,YAAY;GAA4B;EAAW,CAAA;CAChD,CAAA,GACzB,oBAAC,UAAD;EACO;EACL,MAAM,QAAQ;GACZ,WAAW,UAAU,QAAQ,GAAG;GAChC,iBAAiB,GAAG;EACtB;EACA,cAAc,eAAe,QAAQ;EACrC,eAAe,eAAe,OAAO;EACrC,OAAO;CACR,CAAA,CACD,EAAA,CAAA;AAEN"}