Skip to content

External router integration

Contract

To communicate between iframe and its parent site is used window.postMessage function. Means betbook sends message to parent site.

interface NavigationMessage {
  type: "navigation";
  path: string;
  query: Object;
}

Info

{ type: 'navigation', path: '/live/tennis', query: { tab: 'all' } }

where pathname will contain betbook route string. For example: (“/live/tableTennis”, “/events/yurii-volkov-andrii-troian-11691674“)

Message listener example
window.addEventListener("message", data => {
  if (data.type === "navigation") {
    // TODO: handle data.path and repalace route
  }
});

Route refresh

To update browser url with new route use History replaceState API in particular framework router.

To avoid conflicts with parent site routing it would be a good idea to add some route prefix to ‘navigation’ data.

Example

Add prefix /sport to all routes received from betbook. Then full routes for replaceAPI will look like: '/sport/live/tableTennis', '/sport/events/yurii-volkov-andrii-troian-11691674'

React useful hook

If parent site uses react as main framework, we provide useful useNavigation hook based on react-router-dom

Utils from this hooks can be used in non-react integration also, details in jsdocs comments.

import { useCallback } from "react";
import { useLocation, useNavigate } from "react-router-dom";

const IFRAME_PREFIX = "sports";

/**
 * Extracts the language from the given path.
 *
 * @param {string} path - The URL path.
 * @param {Array} languages - Array of available languages.
 * @returns {string|undefined} - The language code if found, otherwise undefined.
 */
export const getUrlLang = (path, languages) => {
  return path.split("/").find(segment => languages.includes(segment));
};

/**
 * Prepares the iframe path by removing language and prefix segments.
 *
 * @param {string} path - The URL path.
 * @param {Array} languages - Array of available language codes.
 * @param {string} fallbackLang - The default language code.
 * @returns {string} - The prepared iframe path (removes lang and prefix)
 */
const prepareIframePath = (path, languages, fallbackLang) => {
  const lang = getUrlLang(path, languages) || fallbackLang;
   const regex = new RegExp(
    `${IFRAME_PREFIX}/${lang}/?|/${lang}/?`,
    'g',
  );
  return path.replace(regex, "");
};

/**
 * Prepares the client path by concatenating prefix, language and iframe path segments.
 *
 * @param {string} path - The iframe path.
 * @param {Array} languages - Array of available language codes.
 * @param {string} fallbackLang - The default language code.
 * @returns {string} - The prepared client path.
 */
const prepareClientPath = (path, languages, fallbackLang) => {
  const lang = getUrlLang(path, languages) || fallbackLang;
  const preparedIframePath = prepareIframePath(path, languages, fallbackLang);
  return preparedIframePath === "/" ? preparedIframePath : `/${IFRAME_PREFIX}/${lang}${preparedIframePath}`;
};

/**
 * Custom hook for client-iframe navigation.
 *
 * @param {string} lang - The current language code.
 * @param {Array} langList - Array of supported language codes.
 * @returns {Object} - Object containing navigation functions and paths.
 */
export const useNavigation = (lang, langList) => {
  const navigateByPath = useNavigate();
  const { pathname } = useLocation();

  const iframePath = prepareIframePath(pathname, langList, lang);
  const clientPath = prepareClientPath(pathname, langList, lang);

  /**
   * Navigates by path.
   *
   * @param {Object} options - Navigation options.
   * @param {string} options.path - The path to navigate to.
   * @param {string} [options.language=lang] - The language code to use.
   */
  const navigate = useCallback(
    ({ path, language = lang }) => {
      navigateByPath(prepareClientPath(path, langList, language), { replace: true });
    },
    [lang, navigateByPath, langList]
  );

  return {
    navigateByPath,
    navigate,
    iframePath,
    clientPath,
  };
};

export default useNavigation;

Note

Client can use hook methods to get paths for parent site and iframe taking sport prefix into an account.

Hook integration in App.jsx file simplified example
// ...imports etc
const prepareUrl = (lang, path = '') => {
  return innerAppHost + `/${lang}` + path;
};

function App() {
  const { lang, setLang, langCodes } = useLanguageService(); // any language service
  const [url, setUrl] = useState(null);
  const { navigate, navigateByPath, iframePath, clientPath } = useNavigation(
    lang,
    langCodes,
  );
  const routerRef = useRef({ prevPath: iframePath });

  useEffect(() => {
    const { prevPath } = routerRef.current;
    setUrl(prepareUrl(lang, prevPath));
    navigate({ path: prevPath, language: lang });
  }, [lang, navigate]);

  useEffect(() => {
    window.addEventListener('message', (data) => {
      if (data.type === 'navigation') {
          if (path === routerRef.current.prevPath) return;
          navigate({ path });
          routerRef.current.prevPath = path;
      }
    });
  }, [navigate]);

  return (
      <iframe
        id="betbook"
        src={url}
      ></iframe>
  );
}

export default App;