services/tabs.jsxjavascript
/*
  Manages external browser tabs/windows opened from within the app (e.g. links
  that should open outside the kiosk view), keyed by URL so a given URL only
  ever has one tab open at a time.

  TabContext: holds no state directly, only exposes openTab/closeTab.
  useTabs:    hook to access openTab/closeTab anywhere in the tree.
  TabProvider: tracks open WindowProxy references per URL in state.
*/

/* eslint-disable react-refresh/only-export-components */
import React, { createContext, useContext, useState, useCallback, useMemo } from "react";
import PropTypes from "prop-types";

const TabContext = createContext();

/**
 * Provides openTab/closeTab (see useTabs) to the component tree, tracking
 * open external tabs/windows per URL.
 */
export const TabProvider = ({ children }) => {
  const [tabs, setTabs] = useState({});

  /**
   * Opens a new tab for `url`, or focuses/reopens the existing one if it's
   * already open (or was closed by the user in the meantime).
   *
   * @param {string} url - Also used as the tabs' cache key.
   * @returns {Window|null} The (possibly newly opened) tab, or null if the
   *   browser blocked the popup.
   */
  const openTab = useCallback(
    (url) => {
      const existingTab = tabs[url];

      // `existingTab.closed` is false as long as the WindowProxy is still open -
      // becomes true if the user closed the tab manually, in which case we
      // treat it as gone and open a fresh one below.
      if (!existingTab || existingTab.closed) {
        // Neues Fenster öffnen
        const newTab = window.open(url, "_blank");
        setTabs((prev) => ({ ...prev, [url]: newTab }));
        return newTab;
      } else {
        existingTab.focus();
        return existingTab;
      }
    },
    [tabs]
  );

  /**
   * Closes the tab tracked for `url`, if it's still open, and forgets it.
   *
   * @param {string} url
   */
  const closeTab = useCallback((url) => {
    setTabs((prev) => {
      const existingTab = prev[url];
      if (existingTab && !existingTab.closed) {
        existingTab.close();
      }

      const updated = { ...prev };
      delete updated[url];
      return updated;
    });
  }, []);

  const value = useMemo(() => ({ openTab, closeTab }), [openTab, closeTab]);

  return <TabContext.Provider value={value}>{children}</TabContext.Provider>;
};

TabProvider.propTypes = {
  children: PropTypes.node.isRequired,
};

/**
 * React hook to access openTab/closeTab. Must be used within a `<TabProvider>`.
 */
export const useTabs = () => useContext(TabContext);