{
  "version": 3,
  "sources": ["pwa_install.js"],
  "sourcesContent": ["/*\n * static/js/pwa_install.js \u2014 Install-prompt orchestration (SNOW-379).\n *\n * Spec \u00A73.2, \u00A79.1.1, \u00A79.1.2. Delays the install nudge until the user\n * has actually experienced the app, then routes them through the right\n * flow for their platform. Push notifications, badges, and reliable\n * storage on iOS all depend on home-screen install \u2014 so the prompt is\n * not a growth hack, it's a prerequisite for the rest of the offline-\n * first experience.\n *\n * Meaningful-action threshold\n * ---------------------------\n * Either satisfies the threshold \u2014 checked whenever engagement is\n * observed, so the nudge fires at the earlier of the two:\n *\n *   * ``regions_viewed >= 2``  \u2014 distinct MicroRegion IDs seen in the\n *                                URL path. Detected by matching the\n *                                bulletin URL pattern.\n *   * ``time_engaged >= 30s``  \u2014 cumulative foreground time. Ticked by\n *                                a 30-second setTimeout scheduled on\n *                                every ``visibilitychange`` \u2192 visible.\n *\n * Platform routing\n * ----------------\n *   * Chromium / Edge (Android + desktop): capture ``beforeinstallprompt``,\n *     defer, reveal the ``#pwa-install-banner`` when the threshold hits.\n *     \"Install\" calls the deferred ``prompt()`` and awaits the user's\n *     choice.\n *   * iOS Safari (feature-detected via ``'standalone' in navigator`` +\n *     UA sniff): no ``beforeinstallprompt`` on iOS, so reveal the\n *     ``#pwa-install-ios`` visual guide instead \u2014 an animated\n *     Share \u2192 Add to Home Screen illustration.\n *\n * State (via localStorage)\n * ------------------------\n *   pwa.install.regions_viewed  \u2014 JSON array of region_id strings\n *   pwa.install.time_engaged_ms \u2014 cumulative foreground ms\n *   pwa.install.dismissed_at    \u2014 ms timestamp of last dismiss; the\n *                                 nudge is suppressed for 30 days\n *   pwa.install.installed_at    \u2014 ms timestamp of confirmed install\n *\n * SNOW-375's IndexedDB ``meta:app`` object store has since landed, but\n * these keys still live in localStorage; they are read/written through\n * the ``storage`` helpers below so a migration would be a one-liner.\n *\n * Persistent storage\n * ------------------\n * Once installed AND (on iOS) notification permission is granted,\n * ``navigator.storage.persist()`` is called to request that the OS keep\n * our IndexedDB / Cache Storage across storage-pressure evictions. The\n * result is logged as ``pwa.storage.persisted``.\n *\n * Telemetry (SNOW-384)\n * --------------------\n * Wired to ``window.pwaTelemetry?.emit(event, properties)``\n * (``static/js/telemetry.js``, SNOW-385) via the local ``emitTelemetry``\n * helper, which always stamps ``properties.platform`` (``'ios'`` |\n * ``'android'`` | ``'desktop'``, UA-sniffed):\n *   pwa.install.prompted                \u2014 our banner was revealed\n *   pwa.install.accepted                \u2014 user accepted the native prompt\n *   pwa.install.dismissed               \u2014 user dismissed our banner (30-day cool-off)\n *   pwa.install.completed               \u2014 ``appinstalled`` fired / standalone launch detected\n *   pwa.install.notifications_granted   \u2014 iOS install completed with Notification.permission\n *                                          already 'granted' (see onInstalled())\n *   pwa.storage.persisted               \u2014 still console.info only; not in\n *                                          analytics.schema.ALLOWED_EVENTS,\n *                                          out of SNOW-384 scope.\n */\n\n(function () {\n  'use strict';\n\n  const REGIONS_KEY = 'pwa.install.regions_viewed';\n  const TIME_KEY = 'pwa.install.time_engaged_ms';\n  const DISMISS_KEY = 'pwa.install.dismissed_at';\n  const INSTALLED_KEY = 'pwa.install.installed_at';\n\n  const REGIONS_THRESHOLD = 2;\n  const TIME_THRESHOLD_MS = 30 * 1000;\n  const DISMISS_COOLOFF_MS = 30 * 24 * 60 * 60 * 1000;\n  const ENGAGEMENT_TICK_MS = 5 * 1000;\n\n  // Bulletin URL pattern the region tracker looks for. Snowdesk routes\n  // are ``/{country}/{region_id}/\u2026`` where region_id includes the\n  // country prefix (``ch-3232``, ``at-07-01``). The regex is generous\n  // on the trailing path so it matches both bulletin pages and any\n  // future region-scoped surfaces.\n  const REGION_PATH_RE = /^\\/[a-z]{2}\\/([a-z]{2}-[a-z0-9-]+)(\\/|$)/i;\n\n  // The ``beforeinstallprompt`` event, deferred until the nudge fires.\n  let deferredPrompt = null;\n\n  /**\n   * Robust localStorage getter \u2014 Safari private mode throws.\n   * @param {string} k\n   * @returns {string | null}\n   */\n  function readItem(k) {\n    try {\n      return localStorage.getItem(k);\n    } catch (_err) {\n      return null;\n    }\n  }\n\n  /**\n   * Robust localStorage setter \u2014 swallows quota / private-mode errors.\n   * @param {string} k\n   * @param {string} v\n   */\n  function writeItem(k, v) {\n    try {\n      localStorage.setItem(k, v);\n    } catch (_err) {\n      // Ignore.\n    }\n  }\n\n  /**\n   * True when the app is running as a home-screen installed shell,\n   * either standard PWA display-mode or iOS Safari's legacy attribute.\n   * @returns {boolean}\n   */\n  function isStandalone() {\n    try {\n      return (\n        window.matchMedia('(display-mode: standalone)').matches ||\n        navigator.standalone === true\n      );\n    } catch (_err) {\n      return false;\n    }\n  }\n\n  /**\n   * Feature-detect iOS Safari. ``'standalone' in navigator`` is\n   * exclusive to iOS Safari; combined with a UA regex we avoid false\n   * positives from desktop Safari devtools emulation.\n   * @returns {boolean}\n   */\n  function isIOS() {\n    try {\n      return (\n        'standalone' in navigator &&\n        /iP(hone|ad|od)/.test(navigator.userAgent) &&\n        !('MSStream' in window)\n      );\n    } catch (_err) {\n      return false;\n    }\n  }\n\n  /**\n   * Coarse platform bucket for the install-funnel telemetry properties\n   * (SNOW-384): ``'ios'`` | ``'android'`` | ``'desktop'``. UA-sniffed \u2014\n   * good enough for a dashboard breakdown, not used for any behavioural\n   * branch (that's ``isIOS()`` / the deferredPrompt presence).\n   * @returns {'ios' | 'android' | 'desktop'}\n   */\n  function installPlatform() {\n    try {\n      if (isIOS()) return 'ios';\n      if (/Android/.test(navigator.userAgent)) return 'android';\n    } catch (_err) {\n      // Fall through to desktop.\n    }\n    return 'desktop';\n  }\n\n  /**\n   * Best-effort telemetry emit \u2014 never breaks the install flow.\n   * @param {string} event\n   * @param {object} [properties]\n   */\n  function emitTelemetry(event, properties) {\n    try {\n      window.pwaTelemetry?.emit(event, { platform: installPlatform(), ...properties });\n    } catch (_err) {\n      // Ignore.\n    }\n  }\n\n  /**\n   * Read the deduped set of region_ids the user has visited.\n   * @returns {string[]}\n   */\n  function loadRegionsViewed() {\n    const raw = readItem(REGIONS_KEY);\n    if (!raw) return [];\n    try {\n      const parsed = JSON.parse(raw);\n      return Array.isArray(parsed) ? parsed.filter((s) => typeof s === 'string') : [];\n    } catch (_err) {\n      return [];\n    }\n  }\n\n  /**\n   * Push a region_id onto the visited list (deduped). Returns the new\n   * length so callers can check the threshold in one call.\n   * @param {string} regionId\n   * @returns {number}\n   */\n  function recordRegionVisit(regionId) {\n    const list = loadRegionsViewed();\n    if (list.includes(regionId)) return list.length;\n    list.push(regionId);\n    writeItem(REGIONS_KEY, JSON.stringify(list));\n    return list.length;\n  }\n\n  /**\n   * Increment the cumulative engaged-time counter.\n   * @param {number} deltaMs\n   * @returns {number}\n   */\n  function addEngagement(deltaMs) {\n    const current = Number(readItem(TIME_KEY)) || 0;\n    const next = current + Math.max(0, deltaMs);\n    writeItem(TIME_KEY, String(next));\n    return next;\n  }\n\n  /**\n   * True when the meaningful-action threshold has been met.\n   * @returns {boolean}\n   */\n  function meetsThreshold() {\n    if (loadRegionsViewed().length >= REGIONS_THRESHOLD) return true;\n    if ((Number(readItem(TIME_KEY)) || 0) >= TIME_THRESHOLD_MS) return true;\n    return false;\n  }\n\n  /**\n   * True when the user recently dismissed the prompt \u2014 suppress nudges\n   * for the 30-day cool-off period. Timestamps outside the window are\n   * treated as if unset.\n   * @returns {boolean}\n   */\n  function isInCoolOff() {\n    const at = Number(readItem(DISMISS_KEY));\n    if (!Number.isFinite(at) || at <= 0) return false;\n    return Date.now() - at < DISMISS_COOLOFF_MS;\n  }\n\n  /**\n   * Detect the region_id on the current URL and record it. No-op when\n   * the URL doesn't match the bulletin pattern.\n   */\n  function trackCurrentRegion() {\n    try {\n      const match = REGION_PATH_RE.exec(window.location.pathname);\n      if (!match) return;\n      recordRegionVisit(match[1].toLowerCase());\n    } catch (_err) {\n      // Ignore.\n    }\n  }\n\n  /**\n   * Reveal the appropriate nudge for the current platform, gated on\n   * threshold + cool-off + install state.\n   */\n  function maybeReveal() {\n    if (isStandalone()) return; // Already installed.\n    if (isInCoolOff()) return; // Recently dismissed.\n    if (!meetsThreshold()) return; // Not enough engagement yet.\n    if (isIOS()) {\n      revealBanner('pwa-install-ios');\n      return;\n    }\n    if (deferredPrompt) revealBanner('pwa-install-banner');\n  }\n\n  /**\n   * Reveal one of the two banner partials. Idempotent \u2014 safe to call\n   * repeatedly.\n   * @param {string} id\n   */\n  function revealBanner(id) {\n    const el = document.getElementById(id);\n    if (!el || !el.classList.contains('hidden')) return;\n    el.classList.remove('hidden');\n    emitTelemetry('pwa.install.prompted', { banner: id });\n  }\n\n  /**\n   * Hide the given banner and stamp the dismiss timestamp so the\n   * 30-day cool-off applies.\n   * @param {string} id\n   */\n  function dismissBanner(id) {\n    const el = document.getElementById(id);\n    if (el) el.classList.add('hidden');\n    writeItem(DISMISS_KEY, String(Date.now()));\n    emitTelemetry('pwa.install.dismissed', { banner: id });\n  }\n\n  /**\n   * Bind the visible controls on the two banners.\n   */\n  function bindBanners() {\n    const android = document.getElementById('pwa-install-banner');\n    if (android) {\n      document\n        .getElementById('pwa-install-accept')\n        ?.addEventListener('click', async () => {\n          if (!deferredPrompt) {\n            dismissBanner('pwa-install-banner');\n            return;\n          }\n          try {\n            deferredPrompt.prompt();\n            const choice = await deferredPrompt.userChoice;\n            if (choice && choice.outcome === 'accepted') {\n              emitTelemetry('pwa.install.accepted');\n            } else {\n              dismissBanner('pwa-install-banner');\n            }\n          } catch (_err) {\n            dismissBanner('pwa-install-banner');\n          } finally {\n            deferredPrompt = null;\n          }\n        });\n    }\n    // SNOW-486: the \"\u00D7\" on both banners is [data-action=\"dismiss\"] inside\n    // a [data-overlay] element, so static/js/overlays.js's shared delegated\n    // handler now owns the actual hide (classList.add('hidden')) \u2014 this\n    // module only needs to run the cool-off-stamp + telemetry side effect\n    // that dismissBanner() previously did inline. The accept-button\n    // failure/rejection paths above still call dismissBanner() directly\n    // (they need the hide too, and there's no click on a data-overlay\n    // descendant to delegate from there).\n    document.addEventListener('overlay:dismissed', (event) => {\n      const el = event.detail && event.detail.overlay;\n      if (!el) return;\n      if (el.id !== 'pwa-install-banner' && el.id !== 'pwa-install-ios') return;\n      writeItem(DISMISS_KEY, String(Date.now()));\n      emitTelemetry('pwa.install.dismissed', { banner: el.id });\n    });\n  }\n\n  /**\n   * Bind the engagement tick \u2014 every 5 seconds while the page is\n   * visible, add that much to the counter and re-check the threshold.\n   */\n  function bindEngagementTick() {\n    let intervalId = null;\n    function start() {\n      if (intervalId != null) return;\n      intervalId = setInterval(() => {\n        addEngagement(ENGAGEMENT_TICK_MS);\n        maybeReveal();\n      }, ENGAGEMENT_TICK_MS);\n    }\n    function stop() {\n      if (intervalId == null) return;\n      clearInterval(intervalId);\n      intervalId = null;\n    }\n    document.addEventListener('visibilitychange', () => {\n      if (document.hidden) stop();\n      else start();\n    });\n    if (!document.hidden) start();\n  }\n\n  /**\n   * Ask the browser to make our storage persistent. Called once we can\n   * plausibly know the user is committed (installed + notification-\n   * permission-granted on iOS).\n   */\n  async function requestPersistentStorage() {\n    try {\n      if (!('storage' in navigator) || typeof navigator.storage.persist !== 'function') {\n        return;\n      }\n      const persisted = await navigator.storage.persist();\n      try {\n        console.info('pwa.storage.persisted', { result: persisted });\n      } catch (_err) {\n        // Ignore.\n      }\n    } catch (_err) {\n      // Ignore.\n    }\n  }\n\n  /**\n   * Called after install detection. Records timestamp, requests\n   * persistent storage.\n   */\n  async function onInstalled() {\n    writeItem(INSTALLED_KEY, String(Date.now()));\n    emitTelemetry('pwa.install.completed');\n    // On iOS, storage.persist() only reliably returns true once\n    // notification permission has been granted. Elsewhere the browser\n    // may auto-grant based on install state.\n    if (isIOS()) {\n      try {\n        if (\n          'Notification' in window &&\n          Notification.permission === 'granted'\n        ) {\n          // SNOW-384: this is the only notification-permission check in\n          // this file \u2014 pwa_install.js never itself calls\n          // Notification.requestPermission() (that lives in the staff\n          // push demo, static/js/push_demo.js, a separate flow). This\n          // observes permission already granted by the time install\n          // completes, which is the funnel signal the dashboard wants.\n          emitTelemetry('pwa.install.notifications_granted');\n          await requestPersistentStorage();\n        }\n      } catch (_err) {\n        // Ignore.\n      }\n    } else {\n      await requestPersistentStorage();\n    }\n  }\n\n  // Capture the browser's install prompt so we can defer it.\n  window.addEventListener('beforeinstallprompt', (evt) => {\n    evt.preventDefault();\n    deferredPrompt = evt;\n    maybeReveal();\n  });\n\n  window.addEventListener('appinstalled', () => {\n    onInstalled().catch(() => {});\n  });\n\n  function ready() {\n    // Standalone launch \u2014 user already installed; fire the post-install\n    // one-time work if we haven't recorded it yet.\n    if (isStandalone() && !readItem(INSTALLED_KEY)) {\n      onInstalled().catch(() => {});\n    }\n    trackCurrentRegion();\n    bindBanners();\n    bindEngagementTick();\n    maybeReveal();\n  }\n\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', ready);\n  } else {\n    ready();\n  }\n})();\n"],
  "mappings": "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",
  "names": ["REGIONS_KEY", "TIME_KEY", "DISMISS_KEY", "INSTALLED_KEY", "REGION_PATH_RE", "deferredPrompt", "readItem", "k", "writeItem", "v", "isStandalone", "isIOS", "installPlatform", "emitTelemetry", "event", "properties", "loadRegionsViewed", "raw", "parsed", "s", "recordRegionVisit", "regionId", "list", "addEngagement", "deltaMs", "next", "meetsThreshold", "isInCoolOff", "at", "trackCurrentRegion", "match", "maybeReveal", "revealBanner", "id", "el", "dismissBanner", "bindBanners", "choice", "bindEngagementTick", "intervalId", "start", "stop", "requestPersistentStorage", "persisted", "onInstalled", "evt", "ready"]
}
