Newer
Older
navi-1 / webclient / src / composables / nativeBridge.js
/**
 * JS bridge into the Android WebView app (window.NaviAndroid, added by
 * MainActivity's NaviBridge). Web push APIs don't exist inside a WebView,
 * so the app shows notifications natively; this composable is the only
 * place that touches the bridge surface.
 */

/** The bridge object or null — never throws in non-WebView contexts. */
export function nativeBridge() {
  return typeof window !== 'undefined' ? window.NaviAndroid ?? null : null
}

/** Bridge present with the full surface. */
export function bridgeAvailable() {
  const b = nativeBridge()
  return !!b && typeof b.notify === 'function' && typeof b.enableBackgroundMode === 'function'
}

const FLAG_KEY = 'navi_push_android'

/** Whether the user turned native push on (persisted locally, no server side). */
export function nativePushFlag() {
  try {
    return localStorage.getItem(FLAG_KEY) === '1'
  } catch {
    return false
  }
}

function setNativePushFlag(on) {
  try {
    if (on) localStorage.setItem(FLAG_KEY, '1')
    else localStorage.removeItem(FLAG_KEY)
  } catch { /* storage unavailable — state stays in-memory */ }
}

/**
 * Run the bridge's permission request through the 'navi-perm-result'
 * CustomEvent the app posts back with {detail: granted}.
 */
export function requestPermissionViaBridge() {
  const bridge = nativeBridge()
  if (!bridge || typeof bridge.requestNotificationPermission !== 'function') {
    return Promise.resolve(false)
  }
  if (bridge.hasNotificationPermission?.()) return Promise.resolve(true)
  return new Promise((resolve) => {
    const onResult = (e) => {
      clearTimeout(timer)
      window.removeEventListener('navi-perm-result', onResult)
      resolve(!!(e && e.detail))
    }
    // Safety valve: if the activity never answers, fail closed.
    const timer = setTimeout(() => {
      window.removeEventListener('navi-perm-result', onResult)
      resolve(false)
    }, 30000)
    window.addEventListener('navi-perm-result', onResult)
    bridge.requestNotificationPermission()
  })
}

/**
 * Raise a native turn-complete notification via the bridge — only when the
 * user turned native push on and the app window is not visible (the
 * foreground session already shows everything on screen).
 */
export function notifyNativeTurn(title, body, url) {
  const bridge = nativeBridge()
  if (!bridge || typeof bridge.notify !== 'function') return
  if (typeof document !== 'undefined' && !document.hidden) return
  let enabled
  try {
    enabled = localStorage.getItem(FLAG_KEY) === '1'
  } catch {
    return
  }
  if (!enabled) return
  try {
    bridge.notify(String(title), String(body), String(url ?? '/'))
  } catch (err) {
    console.warn('[nativeBridge] notify failed', err)
  }
}