{"version":3,"file":"openStaffbaseAware-CJs5uAvX.mjs","names":[],"sources":["../src/links/stripStaffbaseLinkPrefix.ts","../src/links/getInAppOpenLinkTarget.ts","../src/links/isSafeNavigationHref.ts","../src/links/normalizeInAppLinks.ts","../src/links/isPromiseLike.ts","../src/links/tryOpenWithStaffbase.ts","../src/links/openStaffbaseAware.ts"],"sourcesContent":["/**\n * Strips a leading Staffbase link prefix (`/deeplink/` or `/openlink/`) from a\n * URL pathname, leaving the canonical in-app path.\n * @param {string} path - The URL pathname.\n * @returns {string} The path without the Staffbase prefix.\n */\nexport const stripStaffbaseLinkPrefix = (path: string): string => {\n  if (path.startsWith('/deeplink/'))\n    return `/${path.slice('/deeplink/'.length)}`\n  if (path.startsWith('/openlink/'))\n    return `/${path.slice('/openlink/'.length)}`\n  return path\n}\n","import { stripStaffbaseLinkPrefix } from './stripStaffbaseLinkPrefix'\n\n/**\n * Returns an absolute URL string that is safe to pass into Staffbase's\n * `openLink()`. We intentionally do NOT require `/openlink/`; if the platform\n * strips it, links can still open in-app by delegating to `openLink()`. Ported\n * from staffbase-alerts (identical to unack).\n * @param {string | null | undefined} href - Raw href as found on an <a> element.\n * @param {string} baseUrl - Base URL used to resolve relative hrefs (usually window.location.origin).\n * @returns {string | null} An absolute URL string, or null if it should not be handled.\n */\nexport const getInAppOpenLinkTarget = (\n  href: string | null | undefined,\n  baseUrl: string,\n): string | null => {\n  const trimmed = href?.trim()\n  if (!trimmed) return null\n\n  // Ignore anchors and non-navigational links.\n  if (trimmed.startsWith('#')) return null\n\n  // Let the browser handle special protocols.\n  const lower = trimmed.toLowerCase()\n  if (\n    lower.startsWith('mailto:') ||\n    lower.startsWith('tel:') ||\n    lower.startsWith('sms:') ||\n    lower.startsWith('javascript:') ||\n    lower.startsWith('data:')\n  ) {\n    return null\n  }\n\n  try {\n    const base = new URL(baseUrl)\n    const url = new URL(trimmed, base.toString())\n\n    // Canonicalize same-origin links by stripping Staffbase prefixes, but keep them absolute.\n    if (url.origin === base.origin) {\n      const cleanedPath = stripStaffbaseLinkPrefix(url.pathname)\n      const absolute = new URL(\n        `${cleanedPath}${url.search}${url.hash}`,\n        base.origin,\n      )\n      return absolute.toString()\n    }\n\n    // For cross-origin absolute URLs, return as-is.\n    return url.toString()\n  } catch {\n    return null\n  }\n}\n","/**\n * Returns true when an href is safe to pass to a real browser navigation\n * (e.g. `window.location.assign`). Blocks scripted/inline schemes that could\n * execute in the session-bearing webview. Relative URLs and http(s)/mailto/tel\n * are allowed. Promoted from staffbase-alerts.\n * @param {string} href - The href to validate.\n * @returns {boolean} True when the href is safe to navigate to.\n */\nexport const isSafeNavigationHref = (href: string): boolean => {\n  const trimmed = href.trim().toLowerCase()\n  if (!trimmed) return false\n\n  return !(\n    trimmed.startsWith('javascript:') ||\n    trimmed.startsWith('data:') ||\n    trimmed.startsWith('vbscript:')\n  )\n}\n","/**\n * Normalizes links inside Staffbase-rendered HTML so they behave better in mobile\n * apps. On iOS, absolute same-origin links and/or `target=\"_blank\"` can trigger\n * Safari instead of in-app navigation; for same-origin links we rewrite to\n * relative paths and remove target/rel. Ported from staffbase-alerts (the\n * superset): cross-origin `target=\"_blank\"` links are hardened with\n * `rel=\"noopener noreferrer\"` instead of left untouched. The Staffbase origin is\n * passed in (the lib never reads env).\n * @param {HTMLElement} root - The container whose anchors are normalized.\n * @param {string} staffbaseOrigin - The Staffbase origin used to detect same-origin links.\n * @returns {void}\n */\nexport const normalizeInAppLinks = (\n  root: HTMLElement,\n  staffbaseOrigin: string,\n): void => {\n  const origin = staffbaseOrigin?.trim() || ''\n  if (!origin) return\n\n  const anchors = Array.from(root.querySelectorAll('a[href]'))\n\n  for (const anchor of anchors) {\n    const rawHref = anchor.getAttribute('href')\n    if (!rawHref) continue\n\n    // Skip special protocols and anchors.\n    const lower = rawHref.trim().toLowerCase()\n    if (\n      lower.startsWith('#') ||\n      lower.startsWith('mailto:') ||\n      lower.startsWith('tel:') ||\n      lower.startsWith('sms:') ||\n      lower.startsWith('javascript:') ||\n      lower.startsWith('data:')\n    ) {\n      continue\n    }\n\n    try {\n      const url = new URL(rawHref, origin)\n\n      // Only rewrite links that point to the same Staffbase origin.\n      if (url.origin !== origin) {\n        // Harden cross-origin new-tab links against reverse tabnabbing rather\n        // than leaving the authored target/rel untouched.\n        if (anchor.getAttribute('target') === '_blank') {\n          anchor.setAttribute('rel', 'noopener noreferrer')\n        }\n        continue\n      }\n\n      // Convert to a relative URL so the mobile app treats it as in-app navigation.\n      const relative = `${url.pathname}${url.search}${url.hash}`\n      const withoutStaffbasePrefix = relative\n        .replace(/^\\/deeplink\\//, '/')\n        .replace(/^\\/openlink\\//, '/')\n      anchor.setAttribute('href', withoutStaffbasePrefix)\n\n      // Staffbase uses this class in various contexts to mark links as internal.\n      // Adding it here increases the chance that the iOS app routes the navigation in-app.\n      anchor.classList.add('internal-link')\n\n      // Avoid iOS opening Safari for \"new window\" navigation.\n      anchor.removeAttribute('target')\n      anchor.removeAttribute('rel')\n    } catch {\n      // If parsing fails, leave link as-is.\n    }\n  }\n}\n","/**\n * Type guard for Promise-like (thenable) values, used to normalize whatever\n * Staffbase's openLink returns.\n * @param {unknown} value - The value to test.\n * @returns {boolean} True when value has a callable then method.\n */\nexport const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n  value !== null &&\n  typeof value === 'object' &&\n  'then' in value &&\n  typeof (value as PromiseLike<unknown>).then === 'function'\n","import type { TryOpenResult } from '../types/links/TryOpenResult'\nimport type { WindowWithStaffbase } from '../types/links/WindowWithStaffbase'\n\nimport { isPromiseLike } from './isPromiseLike'\n\n/**\n * Attempt to open a link using Staffbase's plugin util.openLink if available.\n * Ported from staffbase-alerts (typed Promise-like guard; smart-search used\n * `as any`).\n * @param {string} href - The target URL to open.\n * @returns {TryOpenResult} Whether Staffbase handled it and a promise if available.\n */\nexport const tryOpenWithStaffbase = (href: string): TryOpenResult => {\n  try {\n    if (typeof window === 'undefined') {\n      return { handled: false }\n    }\n    const w = window as WindowWithStaffbase\n    const open = w?.staffbase?.plugin?.util?.openLink\n    const hasOpenLink = typeof open === 'function'\n    if (!hasOpenLink) return { handled: false }\n    // Pass empty options as the second parameter to align with openLink signature\n    const maybePromise = open(href, {})\n    // If a Promise is returned, normalize it\n    let normalizedPromise: Promise<void> | undefined\n    if (maybePromise && isPromiseLike(maybePromise)) {\n      normalizedPromise = maybePromise as Promise<void>\n    }\n    return { handled: true, promise: normalizedPromise }\n  } catch {\n    return { handled: false }\n  }\n}\n","import { isSafeNavigationHref } from './isSafeNavigationHref'\nimport { tryOpenWithStaffbase } from './tryOpenWithStaffbase'\n\n/**\n * Try to open with Staffbase; if not available, navigate normally in the same\n * tab. Ported from staffbase-alerts (the superset): it guards with\n * isSafeNavigationHref first, so scripted schemes never reach navigation.\n * @param {string} href - The target URL to open.\n * @returns {boolean} True if Staffbase handled the navigation, otherwise false.\n */\nexport const openStaffbaseAware = (href: string): boolean => {\n  if (!href) return false\n\n  // Never navigate to scripted/inline schemes (javascript:, data:, vbscript:),\n  // which would execute in the session-bearing webview.\n  if (!isSafeNavigationHref(href)) return false\n\n  const result = tryOpenWithStaffbase(href)\n  if (result.handled) {\n    // Watchdog: if Staffbase openLink is a no-op, force navigation shortly after\n    try {\n      const beforeHref =\n        typeof window !== 'undefined' ? window.location.href : undefined\n      const timer = setTimeout(() => {\n        try {\n          const afterHref =\n            typeof window !== 'undefined' ? window.location.href : undefined\n          const visibility =\n            typeof document !== 'undefined'\n              ? document.visibilityState\n              : undefined\n          const noChange = beforeHref && afterHref && beforeHref === afterHref\n          const stillVisible = visibility === 'visible'\n          if (noChange && stillVisible && typeof window !== 'undefined') {\n            window.location.assign(href)\n          }\n        } catch {\n          // Ignore watchdog navigation failures.\n        }\n      }, 400)\n      // If the Staffbase promise resolves, cancel the watchdog fallback\n      try {\n        result.promise\n          ?.then(() => {\n            clearTimeout(timer)\n          })\n          .catch(() => {\n            // Keep the watchdog active on rejection.\n          })\n      } catch {\n        // Ignore promise-wiring failures.\n      }\n    } catch {\n      // Ignore watchdog setup failures.\n    }\n    return true\n  }\n  try {\n    if (typeof window !== 'undefined') {\n      window.location.assign(href)\n    }\n  } catch {\n    // Ignore navigation failures.\n  }\n  return false\n}\n"],"mappings":"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"}