104 lines · 3.3 KB · TypeScript
RawDownload
"use client"; import type { ScopedThreadRef } from "@t3tools/contracts";import { useShallow } from "zustand/react/shallow";import { useCallback, useEffect, useRef } from "react"; import { previewBridge } from "~/components/preview/previewBridge";import { usePreviewBridge } from "~/components/preview/usePreviewBridge"; import { useBrowserRecordingStore } from "./browserRecording";import { useBrowserSurfaceStore } from "./browserSurfaceStore";import { acquireDesktopTab } from "./desktopTabLifetime";import { usePreviewWebviewConfig } from "./previewWebviewConfigState"; interface ElectronWebview extends HTMLElement {  src: string;  partition: string;  preload?: string;  webpreferences?: string;  getWebContentsId: () => number;} declare global {  interface HTMLElementTagNameMap {    webview: ElectronWebview;  }} export function HostedBrowserWebview(props: {  readonly threadRef: ScopedThreadRef;  readonly tabId: string;  readonly initialUrl: string | null;}) {  const { threadRef, tabId, initialUrl } = props;  const config = usePreviewWebviewConfig(threadRef.environmentId);  const initialSrcRef = useRef(initialUrl ?? "about:blank");  const webviewRef = useRef<ElectronWebview | null>(null);  const presentation = useBrowserSurfaceStore(useShallow((state) => state.byTabId[tabId] ?? null));  const recording = useBrowserRecordingStore((state) => state.activeTabId === tabId);   usePreviewBridge({ threadRef, tabId });   useEffect(() => acquireDesktopTab(tabId), [tabId]);   const setWebviewRef = useCallback((node: HTMLElement | null) => {    webviewRef.current = node as ElectronWebview | null;    if (node && !node.hasAttribute("allowpopups")) node.setAttribute("allowpopups", "true");  }, []);   useEffect(() => {    const webview = webviewRef.current;    const bridge = previewBridge;    if (!webview || !config || !bridge) return;    const register = () => {      try {        const webContentsId = webview.getWebContentsId();        if (Number.isInteger(webContentsId) && webContentsId > 0) {          void bridge.registerWebview(tabId, webContentsId);        }      } catch {        // A later dom-ready will retry registration.      }    };    webview.addEventListener("dom-ready", register);    register();    return () => webview.removeEventListener("dom-ready", register);  }, [config, tabId]);   if (!config) return null;  const active = presentation?.visible === true && presentation.rect !== null;  const lastRect = presentation?.rect;  const style =    active && lastRect      ? {          left: lastRect.x,          top: lastRect.y,          width: lastRect.width,          height: lastRect.height,          zIndex: 30,          pointerEvents: "auto" as const,        }      : {          left: 0,          top: 0,          width: lastRect?.width ?? 1280,          height: lastRect?.height ?? 800,          zIndex: recording ? 0 : -1,          pointerEvents: "none" as const,        };   return (    <webview      ref={setWebviewRef}      src={initialSrcRef.current}      partition={config.partition}      webpreferences={config.webPreferences}      {...(config.preloadUrl ? { preload: config.preloadUrl } : {})}      data-preview-tab={tabId}      aria-hidden={active ? undefined : true}      className="fixed flex overflow-hidden bg-background"      style={style}    />  );}