{"version":3,"file":"src-HL7K6lv5.mjs","names":["props: ExceptionProperties","serialized: string | undefined","pending: Promise<void> | void","handler: GlobalErrorHandler","properties: EventProperties","properties: Record<string, unknown>","attrs: Record<string, string>","props: EventProperties","_activeSubscriptions: Set<string>","_connectedSDK: LayersReactNative | null","userProps: Record<string, unknown>","previousScreen: string | null","properties: Record<string, unknown>","useEffect: typeof import('react').useEffect | null","core: LayersCore","options: SurveysModuleOptions","featureFlagValues: Record<string, boolean>","SDK_VERSION: string","context: DeviceContext","clipboardData: ClipboardAttribution | null","appOpenProps: Record<string, unknown>","status: ATTStatus","requestATT","updates: DeviceContext","next: DeviceContext","platform: string | undefined","response: Response","payload: Record<string, unknown>","ctx: DeviceContext","merged: Record<string, unknown>","properties: Record<string, unknown>","deviceModel: string","appVersion: string | undefined","AsyncStorage: AsyncStorageStatic | null","lastEmittedState: string | null","SKANManagerImpl","entry: Promise<void>","keys: string[]","_asyncStorage: AsyncStorageStatic | undefined","_asyncStoragePromise: Promise<AsyncStorageStatic | null> | null","_ephemeralInstallId: string | null","text: string | null","out: Record<string, string>","props: Record<string, unknown>","params: Record<string, string>"],"sources":["../src/exceptions.ts","../src/standard-events.ts","../src/superwall.ts","../src/commerce.ts","../src/revenuecat.ts","../src/background-flush.ts","../src/navigation-tracking.ts","../src/surveys.ts","../src/index.ts"],"sourcesContent":["// React Native uncaught-error auto-capture.\n//\n// Installs a React Native global error handler (`ErrorUtils.setGlobalHandler`)\n// that turns every uncaught JS error into a reserved-namespace `$exception`\n// event carrying the same property set the web client emits, then hands the\n// error to the handler that was installed before it. RedBox in development\n// and the native crash in release therefore behave exactly as they did.\n//\n// A fatal error is the one case with a twist: in a release build the\n// previous handler ends the process, so anything the sink queued would be\n// lost unless it is durable first. `onFatal` lets the caller get the event\n// to disk; the previous handler then runs once that settles or\n// `fatalTimeoutMs` passes, whichever comes first. One fatal error is in\n// flight at a time: a second one arriving while the first waits chains at\n// once, so an error loop cannot pile up timers.\n//\n// Unhandled promise rejections are left to React Native itself. LogBox and\n// Hermes own the rejection tracker, and replacing it changes the warnings\n// developers see, so `$exception_promise_rejection` is always `false` here.\n\n/** Cap on the serialized stack length to keep payloads sane. */\nconst MAX_STACK_CHARS = 10_000;\n\n/** Cap on the serialized message length. */\nconst MAX_MESSAGE_CHARS = 4_000;\n\n/** How long a fatal error waits for `onFatal` before the crash proceeds. */\nconst DEFAULT_FATAL_TIMEOUT_MS = 1_000;\n\n/**\n * Properties emitted alongside a `$exception` event. Names mirror the\n * `$exception_*` reservation in the wire protocol and the web client.\n */\nexport interface ExceptionProperties {\n  $exception_type: string;\n  $exception_message: string;\n  $exception_stack?: string;\n  /** Always `false` from this module; see the header. */\n  $exception_promise_rejection: boolean;\n  /** React Native's `isFatal` flag: `true` when the error takes the app down. */\n  $exception_fatal?: boolean;\n  [key: string]: unknown;\n}\n\n/**\n * Sink that the handler delivers `$exception` events to. The SDK passes its\n * own `track()` here; tests pass a spy.\n */\nexport type ExceptionSink = (eventName: '$exception', properties: ExceptionProperties) => void;\n\nexport interface ExceptionInstallOptions {\n  /**\n   * Hook for surfacing diagnostic logs from inside the handler. The SDK wires\n   * this to its `enableDebug`-gated console.warn.\n   */\n  onInternalError?: (message: string, error: unknown) => void;\n  /**\n   * Runs after the sink for a fatal error. The previous handler, which ends\n   * the process in a release build, runs once the returned promise settles or\n   * `fatalTimeoutMs` passes, whichever comes first. The SDK uses it to get the\n   * event onto disk before the app dies. Errors that are not fatal never wait.\n   */\n  onFatal?: () => Promise<void> | void;\n  /** Upper bound on how long a fatal error waits for `onFatal`. @default 1000 */\n  fatalTimeoutMs?: number;\n}\n\ntype GlobalErrorHandler = (error: unknown, isFatal?: boolean) => void;\n\n/** The slice of React Native's `ErrorUtils` global this module uses. */\ninterface ErrorUtilsLike {\n  getGlobalHandler(): GlobalErrorHandler | undefined;\n  setGlobalHandler(handler: GlobalErrorHandler): void;\n}\n\n/**\n * Process-wide: `true` while a fatal error is waiting on `onFatal`. Module\n * level on purpose, so two SDK instances cannot each hold the crash open.\n */\nlet fatalInFlight = false;\n\nfunction findErrorUtils(): ErrorUtilsLike | null {\n  const candidate = (globalThis as { ErrorUtils?: unknown }).ErrorUtils;\n  if (\n    typeof candidate === 'object' &&\n    candidate !== null &&\n    typeof (candidate as ErrorUtilsLike).getGlobalHandler === 'function' &&\n    typeof (candidate as ErrorUtilsLike).setGlobalHandler === 'function'\n  ) {\n    return candidate as ErrorUtilsLike;\n  }\n  return null;\n}\n\nfunction truncate(s: string, max: number): string {\n  return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n\n/**\n * Describe whatever React Native handed the global handler. Errors carry a\n * name, message and stack; anything else thrown (strings, plain objects) is\n * serialized best-effort. Every field is coerced to a string, since Error\n * subclasses in the wild override `message` with numbers and getters.\n */\nexport function buildExceptionProperties(error: unknown, isFatal?: boolean): ExceptionProperties {\n  const props: ExceptionProperties = {\n    $exception_type: 'Error',\n    $exception_message: '',\n    $exception_promise_rejection: false\n  };\n\n  if (error instanceof Error) {\n    props.$exception_type = String(error.name || 'Error');\n    props.$exception_message = truncate(String(error.message ?? String(error)), MAX_MESSAGE_CHARS);\n    if (error.stack) props.$exception_stack = truncate(String(error.stack), MAX_STACK_CHARS);\n  } else if (typeof error === 'string') {\n    props.$exception_message = truncate(error, MAX_MESSAGE_CHARS);\n  } else {\n    // JSON.stringify returns `undefined` for functions and similar inputs even\n    // though TypeScript types it as string; String() covers those.\n    let serialized: string | undefined;\n    try {\n      serialized = JSON.stringify(error);\n    } catch {\n      serialized = undefined;\n    }\n    props.$exception_message = truncate(\n      typeof serialized === 'string' ? serialized : String(error),\n      MAX_MESSAGE_CHARS\n    );\n  }\n\n  if (typeof isFatal === 'boolean') props.$exception_fatal = isFatal;\n  return props;\n}\n\n/**\n * Invoke the consumer-supplied `onInternalError` defensively. A throwing\n * callback must never escape the global handler, where it would be reported\n * as a second uncaught error.\n */\nfunction reportInternalError(\n  options: ExceptionInstallOptions,\n  message: string,\n  error: unknown\n): void {\n  if (!options.onInternalError) return;\n  try {\n    options.onInternalError(message, error);\n  } catch {\n    // Swallow: propagating defeats the containment this module promises.\n  }\n}\n\n/**\n * Run `onFatal`, then `chain` exactly once: when `onFatal` settles, when it\n * throws or returns nothing, or when `fatalTimeoutMs` passes. The timer is\n * the guarantee that a crashed app is never kept alive by a hung write.\n */\nfunction chainAfterFatalWork(options: ExceptionInstallOptions, chain: () => void): void {\n  let chained = false;\n  const chainOnce = (): void => {\n    if (chained) return;\n    chained = true;\n    fatalInFlight = false;\n    try {\n      chain();\n    } catch (e) {\n      // The previous handler threw from a timer or microtask, where a throw\n      // would surface as a fresh uncaught error and re-enter this handler.\n      reportInternalError(options, 'previous global error handler threw', e);\n    }\n  };\n  const timer = setTimeout(chainOnce, options.fatalTimeoutMs ?? DEFAULT_FATAL_TIMEOUT_MS);\n  const finish = (): void => {\n    clearTimeout(timer);\n    chainOnce();\n  };\n\n  fatalInFlight = true;\n  let pending: Promise<void> | void;\n  try {\n    pending = options.onFatal?.();\n  } catch (e) {\n    reportInternalError(options, 'onFatal threw', e);\n    finish();\n    return;\n  }\n  if (!pending || typeof pending.then !== 'function') {\n    finish();\n    return;\n  }\n  pending.then(finish, (e: unknown) => {\n    reportInternalError(options, 'onFatal rejected', e);\n    finish();\n  });\n}\n\n/**\n * Install the React Native global error handler.\n *\n * Returns an idempotent uninstall function that restores the previous\n * handler when ours is still the current one. Returns a no-op uninstall when\n * `ErrorUtils` is absent (Node, Jest without the RN preset, web).\n */\nexport function installExceptionAutoCapture(\n  sink: ExceptionSink,\n  options: ExceptionInstallOptions = {}\n): () => void {\n  const errorUtils = findErrorUtils();\n  if (!errorUtils) {\n    return () => {};\n  }\n\n  const previous = errorUtils.getGlobalHandler();\n  let uninstalled = false;\n\n  const handler: GlobalErrorHandler = (error, isFatal) => {\n    if (!uninstalled) {\n      try {\n        sink('$exception', buildExceptionProperties(error, isFatal));\n      } catch (e) {\n        reportInternalError(options, 'exception auto-capture failed', e);\n      }\n    }\n    // Always hand the error on: RedBox, LogBox and the release crash path all\n    // live behind the handler that was installed before this one.\n    const chain = (): void => {\n      if (typeof previous === 'function') previous(error, isFatal);\n    };\n    if (isFatal === true && !uninstalled && options.onFatal && !fatalInFlight) {\n      chainAfterFatalWork(options, chain);\n      return;\n    }\n    chain();\n  };\n\n  errorUtils.setGlobalHandler(handler);\n\n  return () => {\n    if (uninstalled) return;\n    uninstalled = true;\n    // Restore only while ours is current; a reporter installed on top of us\n    // keeps its place and keeps chaining through `previous` via our handler.\n    if (errorUtils.getGlobalHandler() !== handler) return;\n    if (typeof previous === 'function') {\n      errorUtils.setGlobalHandler(previous);\n    } else {\n      // Nothing to restore, and an inert handler would swallow every later\n      // error. Rethrowing hands each one to the runtime's own uncaught path.\n      errorUtils.setGlobalHandler((error) => {\n        throw error;\n      });\n    }\n  };\n}\n","// Standard event types for Layers Analytics.\n// These events match the canonical Layers event taxonomy\n// and are consistent across all SDK platforms.\nimport type { EventProperties } from '@layers/core-wasm';\n\n/**\n * Predefined standard event name constants.\n *\n * Usage:\n * ```ts\n * import { StandardEvents } from '@layers/react-native';\n * layers.track(StandardEvents.PURCHASE, { amount: 9.99, currency: 'USD' });\n * ```\n */\nexport const StandardEvents = {\n  APP_INSTALL: 'app_install',\n  APP_OPEN: 'app_open',\n  LOGIN: 'login',\n  SIGN_UP: 'sign_up',\n  REGISTER: 'register',\n  PURCHASE: 'purchase_success',\n  ADD_TO_CART: 'add_to_cart',\n  ADD_TO_WISHLIST: 'add_to_wishlist',\n  INITIATE_CHECKOUT: 'initiate_checkout',\n  BEGIN_CHECKOUT: 'begin_checkout',\n  START_TRIAL: 'start_trial',\n  SUBSCRIBE: 'subscribe',\n  LEVEL_START: 'level_start',\n  LEVEL_COMPLETE: 'level_complete',\n  TUTORIAL_COMPLETE: 'tutorial_complete',\n  SEARCH: 'search',\n  VIEW_ITEM: 'view_item',\n  VIEW_CONTENT: 'view_content',\n  SHARE: 'share',\n  DEEP_LINK: 'deep_link_opened',\n  SCREEN_VIEW: 'screen_view',\n  // First-run funnel — see docs/content/docs/concepts/events.mdx.\n  ONBOARDING_START: 'onboarding_start',\n  ONBOARDING_COMPLETE: 'onboarding_complete',\n  PAYWALL_SHOW: 'paywall_show'\n} as const;\n\n/** Union type of all standard event name strings. */\nexport type StandardEventName = (typeof StandardEvents)[keyof typeof StandardEvents];\n\n// ---------------------------------------------------------------------------\n// Typed helper functions for building standard event payloads.\n// Each returns { event: string; properties: EventProperties } suitable for\n// destructuring into a track() call.\n// ---------------------------------------------------------------------------\n\nexport interface StandardEventPayload {\n  event: StandardEventName;\n  properties: EventProperties;\n}\n\n/** Build a login event. */\nexport function loginEvent(method?: string): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (method !== undefined) properties.method = method;\n  return { event: StandardEvents.LOGIN, properties };\n}\n\n/** Build a sign-up event. */\nexport function signUpEvent(method?: string): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (method !== undefined) properties.method = method;\n  return { event: StandardEvents.SIGN_UP, properties };\n}\n\n/** Build a register event. */\nexport function registerEvent(method?: string): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (method !== undefined) properties.method = method;\n  return { event: StandardEvents.REGISTER, properties };\n}\n\n/** Build a purchase event. */\nexport function purchaseEvent(\n  amount: number,\n  currency = 'USD',\n  itemId?: string\n): StandardEventPayload {\n  const properties: EventProperties = { amount, currency };\n  if (itemId !== undefined) properties.item_id = itemId;\n  return { event: StandardEvents.PURCHASE, properties };\n}\n\n/** Build an add-to-cart event. */\nexport function addToCartEvent(itemId: string, price: number, quantity = 1): StandardEventPayload {\n  return {\n    event: StandardEvents.ADD_TO_CART,\n    properties: { item_id: itemId, price, quantity }\n  };\n}\n\n/** Build an add-to-wishlist event. */\nexport function addToWishlistEvent(\n  itemId: string,\n  name?: string,\n  price?: number\n): StandardEventPayload {\n  const properties: EventProperties = { item_id: itemId };\n  if (name !== undefined) properties.name = name;\n  if (price !== undefined) properties.price = price;\n  return { event: StandardEvents.ADD_TO_WISHLIST, properties };\n}\n\n/** Build an initiate-checkout event. */\nexport function initiateCheckoutEvent(\n  value: number,\n  currency = 'USD',\n  itemCount?: number\n): StandardEventPayload {\n  const properties: EventProperties = { value, currency };\n  if (itemCount !== undefined) properties.item_count = itemCount;\n  return { event: StandardEvents.INITIATE_CHECKOUT, properties };\n}\n\n/** Build a start-trial event. */\nexport function startTrialEvent(plan?: string, durationDays?: number): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (plan !== undefined) properties.plan = plan;\n  if (durationDays !== undefined) properties.duration_days = durationDays;\n  return { event: StandardEvents.START_TRIAL, properties };\n}\n\n/** Build a subscribe event. */\nexport function subscribeEvent(\n  plan: string,\n  amount: number,\n  currency = 'USD'\n): StandardEventPayload {\n  return {\n    event: StandardEvents.SUBSCRIBE,\n    properties: { plan, amount, currency }\n  };\n}\n\n/** Build a level-start event. */\nexport function levelStartEvent(level: string): StandardEventPayload {\n  return { event: StandardEvents.LEVEL_START, properties: { level } };\n}\n\n/** Build a level-complete event. */\nexport function levelCompleteEvent(level: string, score?: number): StandardEventPayload {\n  const properties: EventProperties = { level };\n  if (score !== undefined) properties.score = score;\n  return { event: StandardEvents.LEVEL_COMPLETE, properties };\n}\n\n/** Build a tutorial-complete event. */\nexport function tutorialCompleteEvent(name?: string): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (name !== undefined) properties.name = name;\n  return { event: StandardEvents.TUTORIAL_COMPLETE, properties };\n}\n\n/** Build a search event. */\nexport function searchEvent(query: string, resultCount?: number): StandardEventPayload {\n  const properties: EventProperties = { query };\n  if (resultCount !== undefined) properties.result_count = resultCount;\n  return { event: StandardEvents.SEARCH, properties };\n}\n\n/** Build a view-item event. */\nexport function viewItemEvent(\n  itemId: string,\n  name?: string,\n  category?: string\n): StandardEventPayload {\n  const properties: EventProperties = { item_id: itemId };\n  if (name !== undefined) properties.name = name;\n  if (category !== undefined) properties.category = category;\n  return { event: StandardEvents.VIEW_ITEM, properties };\n}\n\n/** Build a view-content event. */\nexport function viewContentEvent(\n  contentId: string,\n  contentType?: string,\n  name?: string\n): StandardEventPayload {\n  const properties: EventProperties = { content_id: contentId };\n  if (contentType !== undefined) properties.content_type = contentType;\n  if (name !== undefined) properties.name = name;\n  return { event: StandardEvents.VIEW_CONTENT, properties };\n}\n\n/** Build a share event. */\nexport function shareEvent(\n  contentType: string,\n  method?: string,\n  contentId?: string\n): StandardEventPayload {\n  const properties: EventProperties = { content_type: contentType };\n  if (method !== undefined) properties.method = method;\n  if (contentId !== undefined) properties.content_id = contentId;\n  return { event: StandardEvents.SHARE, properties };\n}\n\n/** Build a screen-view event. */\nexport function screenViewEvent(name: string, screenClass?: string): StandardEventPayload {\n  const properties: EventProperties = { screen_name: name };\n  if (screenClass !== undefined) properties.screen_class = screenClass;\n  return { event: StandardEvents.SCREEN_VIEW, properties };\n}\n\n// ---------------------------------------------------------------------------\n// First-run funnel helpers. The server reads `paywall_view`, `paywall_viewed`\n// and `paywall_shown` as aliases of `paywall_show`, so apps already sending any\n// of those names keep their history.\n// ---------------------------------------------------------------------------\n\n/** Build an onboarding-start event. `screenName` is the first onboarding screen shown. */\nexport function onboardingStartEvent(screenName?: string): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (screenName !== undefined) properties.screen_name = screenName;\n  return { event: StandardEvents.ONBOARDING_START, properties };\n}\n\n/** Build an onboarding-complete event. `screenName` is the screen the user finished on. */\nexport function onboardingCompleteEvent(screenName?: string): StandardEventPayload {\n  const properties: EventProperties = {};\n  if (screenName !== undefined) properties.screen_name = screenName;\n  return { event: StandardEvents.ONBOARDING_COMPLETE, properties };\n}\n\n/**\n * Build a paywall-show event. `placement` names where the paywall appeared\n * (e.g. `onboarding`, `settings`); `productIds` lists the products offered.\n */\nexport function paywallShowEvent(placement: string, productIds?: string[]): StandardEventPayload {\n  const properties: EventProperties = { placement };\n  if (productIds !== undefined) properties.product_ids = productIds;\n  return { event: StandardEvents.PAYWALL_SHOW, properties };\n}\n","// Superwall integration for @layers/react-native.\n//\n// Provides helper functions that bridge Layers analytics with Superwall's\n// React Native SDK. Does NOT import @superwall/react-native-superwall directly —\n// uses structural typing to avoid a hard dependency.\nimport type { LayersReactNative } from './index.js';\n\n// ---------------------------------------------------------------------------\n// Structural interfaces matching Superwall types (no import required)\n// ---------------------------------------------------------------------------\n\n/** Minimal subset of Superwall's PaywallInfo used by this integration. */\nexport interface SuperwallPaywallInfo {\n  identifier: string;\n  name?: string;\n  url?: string;\n  products?: { id: string }[];\n  experiment?: {\n    id: string;\n    variantId: string;\n  };\n}\n\n/** Minimal subset of Superwall's event info. */\nexport interface SuperwallEventInfo {\n  event: {\n    rawName?: string;\n    type?: string;\n  };\n  params?: Record<string, unknown>;\n}\n\n/** Product info from a Superwall paywall purchase. */\nexport interface SuperwallProduct {\n  productIdentifier?: string;\n  id?: string;\n  price?: number;\n  currencyCode?: string;\n  currency?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Generic event forwarding\n// ---------------------------------------------------------------------------\n\n/**\n * Forward any Superwall event to Layers, prefixed with `superwall_`.\n *\n * Wire this up in your Superwall delegate / event handler:\n * ```ts\n * import Superwall from '@superwall/react-native-superwall';\n * import { superwallOnEvent } from '@layers/react-native';\n *\n * Superwall.instance.setDelegate({\n *   handleSuperwallEvent(eventInfo) {\n *     superwallOnEvent(sdk, eventInfo);\n *   }\n * });\n * ```\n */\nexport function superwallOnEvent(sdk: LayersReactNative, eventInfo: SuperwallEventInfo): void {\n  try {\n    const eventName = eventInfo.event.rawName ?? eventInfo.event.type ?? 'unknown';\n    const properties: Record<string, unknown> = { source: 'superwall' };\n    if (eventInfo.params) {\n      Object.assign(properties, eventInfo.params);\n    }\n    sdk.track(`superwall_${eventName}`, properties);\n  } catch {\n    // safe fail — SDK never crashes the host app\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Typed paywall events\n// ---------------------------------------------------------------------------\n\n/**\n * Track that a paywall was presented to the user.\n *\n * ```ts\n * superwallTrackPresentation(sdk, paywallInfo);\n * ```\n */\nexport function superwallTrackPresentation(\n  sdk: LayersReactNative,\n  paywallInfo: SuperwallPaywallInfo\n): void {\n  try {\n    if (!paywallInfo) return;\n\n    const properties: Record<string, unknown> = {\n      paywall_id: paywallInfo.identifier,\n      placement: paywallInfo.name ?? 'unknown',\n      source: 'superwall'\n    };\n    if (paywallInfo.url) {\n      properties.url = paywallInfo.url;\n    }\n    if (paywallInfo.experiment) {\n      properties.ab_test = {\n        id: paywallInfo.experiment.id,\n        variant: paywallInfo.experiment.variantId\n      };\n    }\n    sdk.track('paywall_show', properties);\n  } catch {\n    // safe fail\n  }\n}\n\n/**\n * Track that a paywall was dismissed.\n *\n * ```ts\n * superwallTrackDismiss(sdk, paywallInfo);\n * ```\n */\nexport function superwallTrackDismiss(\n  sdk: LayersReactNative,\n  paywallInfo: SuperwallPaywallInfo\n): void {\n  try {\n    if (!paywallInfo) return;\n\n    sdk.track('paywall_dismiss', {\n      paywall_id: paywallInfo.identifier,\n      source: 'superwall'\n    });\n  } catch {\n    // safe fail\n  }\n}\n\n/**\n * Track a purchase initiated from a Superwall paywall.\n *\n * ```ts\n * superwallTrackPurchase(sdk, paywallInfo, product);\n * ```\n */\nexport function superwallTrackPurchase(\n  sdk: LayersReactNative,\n  paywallInfo: SuperwallPaywallInfo,\n  product?: SuperwallProduct\n): void {\n  try {\n    const properties: Record<string, unknown> = {\n      paywall_id: paywallInfo?.identifier ?? 'unknown',\n      source: 'superwall'\n    };\n\n    if (product) {\n      const productId = product.productIdentifier ?? product.id;\n      if (productId) properties.product_id = productId;\n      if (product.price != null) properties.price = product.price;\n      const currency = product.currencyCode ?? product.currency;\n      if (currency) properties.currency = currency;\n    }\n\n    sdk.track('paywall_purchase', properties);\n  } catch {\n    // safe fail\n  }\n}\n\n/**\n * Track that a paywall was skipped (e.g. holdout, no rule match).\n *\n * ```ts\n * superwallTrackSkip(sdk, paywallInfo, 'holdout');\n * ```\n */\nexport function superwallTrackSkip(\n  sdk: LayersReactNative,\n  paywallInfo: SuperwallPaywallInfo | null,\n  reason: string\n): void {\n  try {\n    sdk.track('paywall_skip', {\n      paywall_id: paywallInfo?.identifier ?? 'unknown',\n      reason,\n      source: 'superwall'\n    });\n  } catch {\n    // safe fail\n  }\n}\n\n// ---------------------------------------------------------------------------\n// User attributes\n// ---------------------------------------------------------------------------\n\n/**\n * Get Layers attribution data formatted as Superwall user attributes.\n *\n * Pass the returned object to `Superwall.instance.setUserAttributes()`:\n * ```ts\n * const attrs = superwallUserAttributes(sdk);\n * Superwall.instance.setUserAttributes(attrs);\n * ```\n */\nexport function superwallUserAttributes(sdk: LayersReactNative): Record<string, string> {\n  const attrs: Record<string, string> = {};\n  try {\n    const sessionId = sdk.getSessionId();\n    if (sessionId) attrs.layers_session_id = sessionId;\n\n    const userId = sdk.getAppUserId();\n    if (userId) attrs.layers_user_id = userId;\n  } catch {\n    // safe fail\n  }\n  return attrs;\n}\n","// Commerce module for @layers/react-native.\n// Cross-platform purchase, subscription, cart, and refund tracking helpers.\n// Each function accepts a LayersReactNative instance and tracks the appropriate\n// event with standardized properties consistent with the iOS CommerceModule\n// and Android CommerceModule.\nimport type { EventProperties } from '@layers/core-wasm';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Minimal Layers SDK interface required by the commerce module. */\nexport interface CommerceTracker {\n  track(event: string, properties?: EventProperties): void;\n}\n\n/** Purchase details for trackPurchase. */\nexport interface PurchaseParams {\n  productId: string;\n  /** Unit price of the item. Revenue is computed as `price * quantity`. */\n  price: number;\n  currency: string;\n  transactionId?: string;\n  quantity?: number;\n  isRestored?: boolean;\n  store?: string;\n  properties?: EventProperties;\n}\n\n/** Subscription details for trackSubscription. */\nexport interface SubscriptionParams {\n  productId: string;\n  /** Unit price of the subscription. */\n  price: number;\n  currency: string;\n  period?: string;\n  transactionId?: string;\n  isRenewal?: boolean;\n  isTrial?: boolean;\n  subscriptionGroupId?: string;\n  originalTransactionId?: string;\n  properties?: EventProperties;\n}\n\n/** Cart item for order and checkout tracking. */\nexport interface CartItem {\n  productId: string;\n  name: string;\n  price: number;\n  quantity?: number;\n  category?: string;\n}\n\n/** Order details for trackOrder. */\nexport interface OrderParams {\n  orderId: string;\n  items: CartItem[];\n  subtotal: number;\n  currency?: string;\n  tax?: number;\n  shipping?: number;\n  discount?: number;\n  couponCode?: string;\n  properties?: EventProperties;\n}\n\n/** Refund details for trackRefund. */\nexport interface RefundParams {\n  transactionId: string;\n  amount: number;\n  currency: string;\n  reason?: string;\n  properties?: EventProperties;\n}\n\n/** Purchase failure details for trackPurchaseFailed. */\nexport interface PurchaseFailedParams {\n  productId: string;\n  currency: string;\n  errorCode: string | number;\n  errorMessage?: string;\n  properties?: EventProperties;\n}\n\n// ---------------------------------------------------------------------------\n// Purchase tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a successful purchase.\n *\n * ```ts\n * import { trackPurchase } from '@layers/react-native';\n *\n * trackPurchase(sdk, {\n *   productId: 'premium_monthly',\n *   price: 9.99,\n *   currency: 'USD',\n *   transactionId: 'txn_abc123',\n * });\n * ```\n */\nexport function trackPurchase(sdk: CommerceTracker, params: PurchaseParams): void {\n  const quantity = params.quantity ?? 1;\n  const props: EventProperties = {\n    product_id: params.productId,\n    price: params.price,\n    currency: params.currency,\n    quantity,\n    revenue: params.price * quantity,\n    ...params.properties\n  };\n  if (params.transactionId !== undefined) props.transaction_id = params.transactionId;\n  if (params.isRestored !== undefined) props.is_restored = params.isRestored;\n  if (params.store !== undefined) props.store = params.store;\n\n  sdk.track('purchase_success', props);\n}\n\n/**\n * Track a failed purchase attempt.\n */\nexport function trackPurchaseFailed(sdk: CommerceTracker, params: PurchaseFailedParams): void {\n  const props: EventProperties = {\n    product_id: params.productId,\n    currency: params.currency,\n    error_code: params.errorCode,\n    ...params.properties\n  };\n  if (params.errorMessage !== undefined) props.error_message = params.errorMessage;\n\n  sdk.track('purchase_failed', props);\n}\n\n// ---------------------------------------------------------------------------\n// Subscription tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a subscription purchase or renewal.\n *\n * ```ts\n * import { trackSubscription } from '@layers/react-native';\n *\n * trackSubscription(sdk, {\n *   productId: 'pro_annual',\n *   price: 49.99,\n *   currency: 'USD',\n *   period: 'P1Y',\n * });\n * ```\n */\nexport function trackSubscription(sdk: CommerceTracker, params: SubscriptionParams): void {\n  const props: EventProperties = {\n    product_id: params.productId,\n    price: params.price,\n    currency: params.currency,\n    quantity: 1,\n    revenue: params.price,\n    ...params.properties\n  };\n  if (params.transactionId !== undefined) props.transaction_id = params.transactionId;\n  if (params.period !== undefined) props.period = params.period;\n  if (params.isRenewal !== undefined) props.is_renewal = params.isRenewal;\n  if (params.isTrial !== undefined) props.is_trial = params.isTrial;\n  if (params.subscriptionGroupId !== undefined)\n    props.subscription_group_id = params.subscriptionGroupId;\n  if (params.originalTransactionId !== undefined)\n    props.original_transaction_id = params.originalTransactionId;\n\n  sdk.track('subscribe', props);\n}\n\n// ---------------------------------------------------------------------------\n// Order / cart tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a completed order with multiple line items.\n */\nexport function trackOrder(sdk: CommerceTracker, params: OrderParams): void {\n  const currency = params.currency ?? 'USD';\n  let total = params.subtotal;\n  if (params.tax !== undefined) total += params.tax;\n  if (params.shipping !== undefined) total += params.shipping;\n  if (params.discount !== undefined) total -= params.discount;\n\n  const props: EventProperties = {\n    order_id: params.orderId,\n    subtotal: params.subtotal,\n    total,\n    currency,\n    item_count: params.items.length,\n    revenue: total,\n    product_ids: params.items.map((i) => i.productId).join(','),\n    ...params.properties\n  };\n  if (params.tax !== undefined) props.tax = params.tax;\n  if (params.shipping !== undefined) props.shipping = params.shipping;\n  if (params.discount !== undefined) props.discount = params.discount;\n  if (params.couponCode !== undefined) props.coupon_code = params.couponCode;\n\n  sdk.track('purchase_success', props);\n}\n\n/**\n * Track an item being added to the cart.\n */\nexport function trackAddToCart(\n  sdk: CommerceTracker,\n  item: CartItem,\n  properties?: EventProperties\n): void {\n  const quantity = item.quantity ?? 1;\n  const props: EventProperties = {\n    product_id: item.productId,\n    product_name: item.name,\n    price: item.price,\n    quantity,\n    value: item.price * quantity,\n    ...properties\n  };\n  if (item.category !== undefined) props.category = item.category;\n\n  sdk.track('add_to_cart', props);\n}\n\n/**\n * Track an item being removed from the cart.\n */\nexport function trackRemoveFromCart(\n  sdk: CommerceTracker,\n  item: CartItem,\n  properties?: EventProperties\n): void {\n  const quantity = item.quantity ?? 1;\n  const props: EventProperties = {\n    product_id: item.productId,\n    product_name: item.name,\n    price: item.price,\n    quantity,\n    ...properties\n  };\n  if (item.category !== undefined) props.category = item.category;\n\n  sdk.track('remove_from_cart', props);\n}\n\n/**\n * Track beginning the checkout flow.\n */\nexport function trackBeginCheckout(\n  sdk: CommerceTracker,\n  items: CartItem[],\n  currency = 'USD',\n  properties?: EventProperties\n): void {\n  const total = items.reduce((sum, item) => sum + item.price * (item.quantity ?? 1), 0);\n  const props: EventProperties = {\n    item_count: items.length,\n    value: total,\n    currency,\n    product_ids: items.map((i) => i.productId).join(','),\n    ...properties\n  };\n\n  sdk.track('begin_checkout', props);\n}\n\n// ---------------------------------------------------------------------------\n// Product view tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track viewing a product detail page.\n */\nexport function trackViewProduct(\n  sdk: CommerceTracker,\n  productId: string,\n  name: string,\n  price: number,\n  currency = 'USD',\n  category?: string,\n  properties?: EventProperties\n): void {\n  const props: EventProperties = {\n    product_id: productId,\n    product_name: name,\n    price,\n    currency,\n    ...properties\n  };\n  if (category !== undefined) props.category = category;\n\n  sdk.track('view_item', props);\n}\n\n// ---------------------------------------------------------------------------\n// Refund tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a refund.\n */\nexport function trackRefund(sdk: CommerceTracker, params: RefundParams): void {\n  const props: EventProperties = {\n    transaction_id: params.transactionId,\n    amount: params.amount,\n    currency: params.currency,\n    ...params.properties\n  };\n  if (params.reason !== undefined) props.reason = params.reason;\n\n  sdk.track('refund', props);\n}\n","// RevenueCat integration for @layers/react-native.\n// Provides helper functions that bridge Layers to RevenueCat's React Native SDK.\n// Does NOT import RevenueCat directly — consumers pass their Purchases instance.\nimport type { LayersReactNative } from './index.js';\n\n/**\n * Minimal interface for RevenueCat's CustomerInfo object.\n * Avoids a hard dependency on `react-native-purchases`.\n */\nexport interface RevenueCatCustomerInfo {\n  readonly activeSubscriptions: string[];\n  readonly originalAppUserId: string;\n}\n\n/**\n * Minimal interface for RevenueCat's PurchasesPackage object.\n */\nexport interface RevenueCatPackage {\n  readonly product: {\n    readonly identifier: string;\n    readonly price: number;\n    readonly currencyCode: string;\n  };\n}\n\n/**\n * Options for configuring the RevenueCat integration.\n */\nexport interface RevenueCatConfig {\n  /** The Layers SDK instance to bridge events to. */\n  sdk: LayersReactNative;\n  /**\n   * Callback invoked when a new subscription is detected.\n   * By default, new subscriptions are tracked as `subscription_start` events.\n   * Set to `null` to disable automatic subscription tracking.\n   */\n  onSubscriptionStart?: ((productId: string) => void) | null;\n}\n\n// Internal state\nlet _isConnected = false;\nlet _activeSubscriptions: Set<string> = new Set();\nlet _isInitialLoadDone = false;\nlet _connectedSDK: LayersReactNative | null = null;\n\n/**\n * Connect Layers to your existing RevenueCat Purchases instance.\n *\n * Listens for customer info updates to detect new subscriptions and syncs\n * subscriber status to Layers user properties.\n *\n * @example\n * ```ts\n * import Purchases from 'react-native-purchases';\n * import { LayersReactNative, connectRevenueCat } from '@layers/react-native';\n *\n * const sdk = new LayersReactNative();\n * await layers.init({ appId: 'your-app-id', environment: 'production' });\n *\n * connectRevenueCat({\n *   sdk,\n *   purchases: Purchases,\n * });\n * ```\n *\n * @param config - Configuration including the SDK instance.\n * @param purchases - The RevenueCat Purchases instance (duck-typed to avoid hard dependency).\n */\nexport function connectRevenueCat(\n  config: RevenueCatConfig,\n  purchases: {\n    addCustomerInfoUpdateListener: (\n      listener: (info: RevenueCatCustomerInfo) => void\n    ) => { remove: () => void } | void;\n    getCustomerInfo: () => Promise<{ customerInfo: RevenueCatCustomerInfo }>;\n  }\n): void {\n  if (_isConnected) return;\n\n  try {\n    if (!purchases) return;\n    if (typeof purchases.addCustomerInfoUpdateListener !== 'function') return;\n    if (typeof purchases.getCustomerInfo !== 'function') return;\n\n    _connectedSDK = config.sdk;\n\n    // Listen for future customer info updates\n    purchases.addCustomerInfoUpdateListener((info: RevenueCatCustomerInfo) => {\n      handleCustomerInfoUpdate(config, info, false);\n    });\n\n    // Fetch current customer info for initial state\n    purchases\n      .getCustomerInfo()\n      .then(({ customerInfo }) => {\n        handleCustomerInfoUpdate(config, customerInfo, true);\n      })\n      .catch(() => {\n        // Silently ignore errors fetching initial customer info\n      });\n\n    _isConnected = true;\n  } catch {\n    // Never crash the host app\n  }\n}\n\n/**\n * Track a RevenueCat package purchase manually.\n *\n * @example\n * ```ts\n * const offerings = await Purchases.getOfferings();\n * const pkg = offerings.current?.availablePackages[0];\n * if (pkg) trackRevenueCatPurchase(sdk, pkg);\n * ```\n */\nexport function trackRevenueCatPurchase(\n  sdk: LayersReactNative,\n  rcPackage: RevenueCatPackage,\n  store?: string\n): void {\n  try {\n    if (!rcPackage?.product) return;\n\n    let resolvedStore = store ?? 'app_store';\n    if (!store) {\n      try {\n        const { Platform } = require('react-native');\n        resolvedStore = Platform.OS === 'ios' ? 'app_store' : 'play_store';\n      } catch {\n        // Fallback to app_store if Platform is not available\n      }\n    }\n\n    sdk.track('purchase_success', {\n      product_id: rcPackage.product.identifier ?? '',\n      price: rcPackage.product.price ?? 0,\n      currency: rcPackage.product.currencyCode ?? 'USD',\n      store: resolvedStore,\n      source: 'revenuecat'\n    });\n  } catch {\n    // Never crash the host app\n  }\n}\n\n/**\n * Manually sync RevenueCat subscriber attributes to Layers user properties.\n *\n * This is called automatically when using `connectRevenueCat()`, but can also\n * be called manually if needed.\n */\nexport function syncRevenueCatAttributes(\n  sdk: LayersReactNative,\n  customerInfo: RevenueCatCustomerInfo\n): void {\n  try {\n    const isSubscriber =\n      Array.isArray(customerInfo.activeSubscriptions) &&\n      customerInfo.activeSubscriptions.length > 0;\n\n    const userProps: Record<string, unknown> = {\n      is_subscriber: isSubscriber\n    };\n\n    if (customerInfo.originalAppUserId) {\n      userProps.revenuecat_original_app_user_id = customerInfo.originalAppUserId;\n    }\n\n    void sdk.setUserProperties(userProps);\n  } catch {\n    // Never crash the host app\n  }\n}\n\n/**\n * Reset RevenueCat integration state. For testing only.\n */\nexport function resetRevenueCatForTesting(): void {\n  _isConnected = false;\n  _activeSubscriptions = new Set();\n  _isInitialLoadDone = false;\n  _connectedSDK = null;\n}\n\n/**\n * Whether the RevenueCat integration is currently connected.\n */\nexport function isRevenueCatConnected(): boolean {\n  return _isConnected;\n}\n\n// Internal: handle customer info updates\nfunction handleCustomerInfoUpdate(\n  config: RevenueCatConfig,\n  info: RevenueCatCustomerInfo,\n  isInitialLoad: boolean\n): void {\n  try {\n    const currentSubs = new Set<string>(\n      Array.isArray(info.activeSubscriptions) ? info.activeSubscriptions : []\n    );\n\n    if (isInitialLoad && _isInitialLoadDone) return;\n\n    // Track new subscriptions (only after initial load)\n    if (!isInitialLoad && _isInitialLoadDone && _connectedSDK) {\n      for (const subId of currentSubs) {\n        if (!_activeSubscriptions.has(subId)) {\n          if (config.onSubscriptionStart !== null) {\n            if (config.onSubscriptionStart) {\n              config.onSubscriptionStart(subId);\n            } else {\n              _connectedSDK.track('subscription_start', {\n                product_id: subId,\n                source: 'revenuecat'\n              });\n            }\n          }\n        }\n      }\n    }\n\n    _activeSubscriptions = currentSubs;\n    _isInitialLoadDone = true;\n\n    // Sync user properties\n    if (_connectedSDK) {\n      syncRevenueCatAttributes(_connectedSDK, info);\n    }\n  } catch {\n    // Never crash the host app\n  }\n}\n","// Background flush support for the Layers React Native SDK.\n//\n// Since React Native cannot directly access BGAppRefreshTask (iOS) or\n// WorkManager (Android) from JavaScript, this module provides handler\n// factories compatible with popular background task libraries:\n//\n// - `react-native-background-fetch` (bare RN projects)\n// - `expo-task-manager` (Expo managed workflow)\n//\n// The handler simply calls flush() on the SDK instance when invoked\n// by the OS-scheduled background task.\n//\n// ## Usage with react-native-background-fetch\n//\n// ```typescript\n// import BackgroundFetch from 'react-native-background-fetch';\n// import { createBackgroundFlushHandler, BACKGROUND_FLUSH_TASK_NAME } from '@layers/react-native';\n//\n// const handler = createBackgroundFlushHandler(() => sdkInstance);\n// BackgroundFetch.configure(\n//   { minimumFetchInterval: 15, taskId: BACKGROUND_FLUSH_TASK_NAME },\n//   handler,\n//   (taskId) => BackgroundFetch.finish(taskId)\n// );\n// ```\n//\n// ## Usage with expo-task-manager\n//\n// ```typescript\n// import * as TaskManager from 'expo-task-manager';\n// import { registerExpoBackgroundFlush, BACKGROUND_FLUSH_TASK_NAME } from '@layers/expo';\n//\n// registerExpoBackgroundFlush(TaskManager, () => sdkInstance);\n//\n// // In app.json or app.config.js, register the background fetch task:\n// // (also requires expo-background-fetch to schedule it)\n// ```\nimport type { LayersReactNative } from './index.js';\n\n/**\n * Task identifier for Layers background flush.\n *\n * Use this constant when registering the background task with\n * `react-native-background-fetch`, `expo-task-manager`, or any\n * other background task scheduler.\n */\nexport const BACKGROUND_FLUSH_TASK_NAME = 'com.layers.sdk.background-flush';\n\n/**\n * Minimum recommended interval (in minutes) between background flushes.\n * Both iOS and Android enforce a minimum of ~15 minutes for background\n * fetch tasks.\n */\nexport const BACKGROUND_FLUSH_MIN_INTERVAL_MINUTES = 15;\n\n/**\n * Result type returned by the background flush handler.\n * Compatible with both `react-native-background-fetch` and\n * `expo-task-manager` result conventions.\n */\nexport interface BackgroundFlushResult {\n  /** Whether the flush completed successfully. */\n  success: boolean;\n  /** Number of events flushed, if available. */\n  eventsFlushed?: number;\n}\n\n/**\n * Creates a background flush handler function.\n *\n * The returned async function, when called by a background task scheduler,\n * will flush any queued events via the Layers SDK. It is safe to call even\n * if the SDK is not initialized (it will silently no-op).\n *\n * @param getSDK - A getter that returns the current LayersReactNative\n *   instance, or `null` if the SDK is not yet initialized.\n * @returns An async handler suitable for use with background task libraries.\n *\n * @example\n * ```typescript\n * const handler = createBackgroundFlushHandler(() => mySDKInstance);\n * ```\n */\nexport function createBackgroundFlushHandler(\n  getSDK: () => LayersReactNative | null\n): () => Promise<BackgroundFlushResult> {\n  return async (): Promise<BackgroundFlushResult> => {\n    const sdk = getSDK();\n    if (!sdk) {\n      return { success: false };\n    }\n\n    try {\n      await sdk.flush();\n      return { success: true };\n    } catch {\n      return { success: false };\n    }\n  };\n}\n\n/**\n * Registers a background flush task with `expo-task-manager`.\n *\n * This must be called at module scope (outside of any component) as\n * `expo-task-manager` requires tasks to be defined before the app mounts.\n *\n * @param taskManager - The `expo-task-manager` module (import * as TaskManager from 'expo-task-manager').\n * @param getSDK - A getter that returns the current LayersReactNative\n *   instance, or `null` if the SDK is not yet initialized.\n *\n * @example\n * ```typescript\n * import * as TaskManager from 'expo-task-manager';\n * import { registerExpoBackgroundFlush } from '@layers/expo';\n *\n * let sdk: LayersReactNative | null = null;\n * registerExpoBackgroundFlush(TaskManager, () => sdk);\n *\n * // Later, in your app initialization:\n * sdk = new LayersReactNative({ appId: '...', environment: 'production' });\n * await layers.init();\n * ```\n */\nexport function registerExpoBackgroundFlush(\n  taskManager: { defineTask: (taskName: string, handler: () => Promise<void>) => void },\n  getSDK: () => LayersReactNative | null\n): void {\n  const handler = createBackgroundFlushHandler(getSDK);\n  taskManager.defineTask(BACKGROUND_FLUSH_TASK_NAME, async () => {\n    await handler();\n  });\n}\n","// React Navigation auto-capture (Tier 2).\n//\n// Two integration shapes are exposed:\n//\n// 1. `useLayersNavigationTracking(sdk, navigationRef)` — a React hook\n//    that subscribes to a React Navigation `NavigationContainerRef.onStateChange`\n//    and emits a `$screen_view` event each time the current route changes.\n//    The hook also tracks the previous screen so the event payload can\n//    carry `previous_screen_name` / `previous_screen_class` (useful for\n//    funnel analysis without joining adjacent events).\n//\n// 2. `createNavigationListener(sdk)` — a lower-level factory returning a\n//    `(state) => void` callback suitable for passing directly to\n//    `<NavigationContainer onStateChange={...}>` or\n//    `navigationRef.addListener('state', ...)`. The callback maintains\n//    its own previous-route closure so consumers don't need to manage\n//    state.\n//\n// Both shapes do the same thing — the hook is the convenience wrapper.\n//\n// Resilience:\n// - All of these helpers `require('react')` and `require('@react-navigation/...')`\n//   lazily so the package can be imported in environments where those\n//   peer dependencies are missing without exploding at import time.\n// - If react-navigation is not installed, `createNavigationListener`\n//   still returns a callable function (no-op) so consumers can wire it\n//   up unconditionally.\nimport type { EventProperties } from '@layers/core-wasm';\n\n/** Minimal SDK surface — supplied by LayersReactNative. */\nexport interface NavigationTrackingHooks {\n  screen(screenName: string, properties?: EventProperties): void;\n  enableDebug?: boolean;\n}\n\n/**\n * Internal route shape — matches React Navigation's\n * `NavigationState.routes[i]` structure but only the fields we read.\n */\ninterface RouteSnapshot {\n  name: string;\n  params?: Record<string, unknown>;\n}\n\n/** State shape we pull from `NavigationState`. */\ninterface NavigationStateSnapshot {\n  index?: number;\n  routes?: Array<RouteSnapshot & { state?: NavigationStateSnapshot }>;\n}\n\n/**\n * Build a stateful listener that emits `$screen_view` whenever the active\n * route changes. The returned function should be passed to React\n * Navigation's `onStateChange` prop or `navigationRef.addListener('state',\n * cb)`. It returns nothing — the SDK records the event side-effecting.\n *\n * The listener:\n * - resolves the deepest active route via `getActiveRouteName()`\n *   semantics so nested stack/tab navigators correctly report the\n *   leaf screen\n * - de-dupes consecutive identical names (some RN versions emit the\n *   same state twice on re-renders)\n * - attaches route params + previous screen on the screen event\n */\nexport function createNavigationListener(sdk: NavigationTrackingHooks) {\n  let previousScreen: string | null = null;\n  return function onStateChange(state: NavigationStateSnapshot | undefined): void {\n    if (!state) return;\n    const currentRoute = resolveActiveRoute(state);\n    if (!currentRoute) return;\n    if (currentRoute.name === previousScreen) return; // de-dupe\n    const properties: Record<string, unknown> = {\n      ...(currentRoute.params ?? {}),\n      screen_name: currentRoute.name\n    };\n    if (previousScreen) {\n      properties.previous_screen_name = previousScreen;\n    }\n    try {\n      sdk.screen(currentRoute.name, properties);\n      if (sdk.enableDebug) {\n        console.log(`[Layers] $screen_view: ${currentRoute.name}`);\n      }\n    } catch {\n      // best-effort — never blow up navigation\n    }\n    previousScreen = currentRoute.name;\n  };\n}\n\n/**\n * React hook that subscribes to a React Navigation NavigationContainerRef\n * and emits a `$screen_view` for each route change. Pass the SDK instance\n * and the same `navigationRef` you give to `<NavigationContainer ref={...}>`.\n *\n * ```tsx\n * import { createNavigationContainerRef } from '@react-navigation/native';\n * const navigationRef = createNavigationContainerRef();\n *\n * function Root() {\n *   useLayersNavigationTracking(layers, navigationRef);\n *   return <NavigationContainer ref={navigationRef}>...</NavigationContainer>;\n * }\n * ```\n *\n * The hook is implemented via a lazy `require('react')` so it only fails\n * if React itself is unavailable — consumers without React Navigation can\n * still call it and it will silently no-op.\n */\nexport function useLayersNavigationTracking(\n  sdk: NavigationTrackingHooks,\n  navigationRef: { addListener?: (event: string, cb: (...args: unknown[]) => void) => () => void }\n): void {\n  let useEffect: typeof import('react').useEffect | null = null;\n  try {\n    // eslint-disable-next-line @typescript-eslint/no-require-imports\n    const React = require('react') as typeof import('react');\n    useEffect = React.useEffect;\n  } catch {\n    // React not available — silently no-op.\n    return;\n  }\n  if (!useEffect) return;\n\n  // Bind the callback once per (sdk, navigationRef) pair. The previous-\n  // screen state lives inside `createNavigationListener`'s closure so\n  // each useEffect run starts fresh.\n  useEffect(() => {\n    if (!navigationRef?.addListener) return undefined;\n    const listener = createNavigationListener(sdk);\n    const unsubscribe = navigationRef.addListener('state', (event: unknown) => {\n      // React Navigation passes either `{ data: { state } }` (newer) or\n      // the raw state object (older). Handle both shapes.\n      const maybeData = (event as { data?: { state?: NavigationStateSnapshot } } | undefined)?.data;\n      const state = maybeData?.state ?? (event as NavigationStateSnapshot | undefined);\n      listener(state);\n    });\n    return () => {\n      try {\n        if (typeof unsubscribe === 'function') unsubscribe();\n      } catch {\n        // best-effort\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [sdk, navigationRef]);\n}\n\n/**\n * Walk a NavigationState tree and return the active leaf route. Mirrors\n * React Navigation's own `getActiveRouteName()` helper without taking a\n * peer-dep on `@react-navigation/native`.\n *\n * Exported for testing.\n * @internal\n */\nexport function resolveActiveRoute(\n  state: NavigationStateSnapshot | undefined\n): RouteSnapshot | null {\n  if (!state || !Array.isArray(state.routes) || state.routes.length === 0) {\n    return null;\n  }\n  const idx = typeof state.index === 'number' ? state.index : state.routes.length - 1;\n  const route = state.routes[idx];\n  if (!route) return null;\n  if (route.state) {\n    const inner = resolveActiveRoute(route.state);\n    if (inner) return inner;\n  }\n  return { name: route.name, ...(route.params != null && { params: route.params }) };\n}\n","// Tier 8 — In-product surveys & messaging on React Native.\n//\n// Owns:\n//   - Delegating to the WASM survey manager for definitions/targeting/auto-events\n//     (same Rust core as web, no native bridge required).\n//   - State for the eligible survey + show/dismiss callbacks. The actual\n//     `<Modal>` chrome lives in `surveys-component.tsx` (a react-native render\n//     component) so consumer apps that don't render UI never bundle React.\n//\n// The chrome is a pure-JS `<Modal>` (per the Tier 8 brief — \"pure RN\n// simpler, ship that\"). No native bridge, no `UIAlertController` /\n// `BottomSheetDialogFragment` wiring.\nimport type {\n  LayersCore,\n  SurveyDefinition,\n  SurveyResponse,\n  SurveyTargetingContext\n} from '@layers/core-wasm';\n\nexport interface SurveysModuleOptions {\n  enabled?: boolean;\n  /** Caller-supplied person properties for targeting. */\n  personProperties?: Record<string, unknown>;\n  /** Caller-supplied feature flag resolver. */\n  resolveFeatureFlag?: (flagKey: string) => boolean | string | null | undefined;\n  /**\n   * Optional `AsyncStorage`-style persistence for the show-history map.\n   * The RN SDK passes its existing `AsyncStorage` adapter automatically.\n   */\n  persistence?: {\n    read(key: string): Promise<string | null>;\n    write(key: string, value: string): Promise<void>;\n  };\n}\n\nconst SHOW_HISTORY_STORAGE_KEY = 'layers:surveys:show-history';\n\n/**\n * Public surveys API exposed via `LayersReactNative.surveys`.\n *\n * Render the actual chrome by mounting `<LayersSurveyModal />` from\n * `@layers/react-native/surveys-component` somewhere in your app tree\n * and binding it to the eligible survey via `surveys.getActive()`.\n */\nexport class SurveysModule {\n  private hydrated = false;\n  private listeners: Set<(active: SurveyDefinition[]) => void> = new Set();\n\n  constructor(\n    private readonly core: LayersCore,\n    private readonly options: SurveysModuleOptions = {}\n  ) {\n    if (this.options.persistence) {\n      void this.hydrate();\n    } else {\n      this.hydrated = true;\n    }\n  }\n\n  get enabled(): boolean {\n    return this.options.enabled !== false;\n  }\n\n  knownIds(): string[] {\n    return this.core.knownSurveyIds();\n  }\n\n  /**\n   * Return surveys currently eligible to show. Builds the targeting context\n   * from caller-supplied person properties + feature flag values. URL\n   * targeting is web-only so it's omitted.\n   */\n  getActive(): SurveyDefinition[] {\n    if (!this.enabled) return [];\n    return this.core.getActiveSurveys(this.buildContext());\n  }\n\n  /** Mark a survey as shown — emits `survey shown` auto-event. */\n  markShown(surveyId: string): void {\n    this.core.markSurveyShown(surveyId);\n    void this.persistShowHistory();\n    this.notify();\n  }\n\n  /** Mark a survey as dismissed — emits `survey dismissed` auto-event. */\n  markDismissed(surveyId: string): void {\n    this.core.markSurveyDismissed(surveyId);\n    this.notify();\n  }\n\n  /** Submit a survey response — emits `survey sent` auto-event. */\n  submit(surveyId: string, response: SurveyResponse): void {\n    this.core.submitSurveyResponse(surveyId, response);\n    this.notify();\n  }\n\n  /**\n   * Subscribe to changes in the eligible-surveys list. The callback fires\n   * after every show/dismiss/submit and after the show-history is hydrated\n   * from persistence on init.\n   */\n  subscribe(listener: (active: SurveyDefinition[]) => void): () => void {\n    this.listeners.add(listener);\n    // Fire immediately so the consumer sees the current state.\n    try {\n      listener(this.getActive());\n    } catch {\n      // ignore\n    }\n    return () => {\n      this.listeners.delete(listener);\n    };\n  }\n\n  configure(patch: Partial<SurveysModuleOptions>): void {\n    Object.assign(this.options, patch);\n    this.notify();\n  }\n\n  // ── Internals ──────────────────────────────────────────────────────\n\n  private buildContext(): SurveyTargetingContext {\n    const featureFlagValues: Record<string, boolean> = {};\n    const resolver = this.options.resolveFeatureFlag;\n    if (resolver) {\n      // Walk ALL definitions (not just active ones — flag-gated surveys are\n      // never active until their flag is resolved, so iterating the active\n      // list creates a circular dependency).\n      for (const def of this.allDefinitions()) {\n        const flag = def?.targeting?.feature_flag;\n        if (flag) {\n          const v = resolver(flag);\n          if (typeof v === 'boolean') featureFlagValues[flag] = v;\n          else if (typeof v === 'string') featureFlagValues[flag] = v.length > 0 && v !== 'false';\n        }\n      }\n    }\n    return {\n      person_properties: this.options.personProperties ?? {},\n      feature_flag_values: featureFlagValues\n    };\n  }\n\n  private allDefinitions(): SurveyDefinition[] {\n    try {\n      return JSON.parse(this.core.surveyDefinitionsJson()) as SurveyDefinition[];\n    } catch {\n      return [];\n    }\n  }\n\n  private async hydrate(): Promise<void> {\n    if (this.hydrated) return;\n    try {\n      const raw = await this.options.persistence?.read(SHOW_HISTORY_STORAGE_KEY);\n      if (raw) this.core.seedSurveyShowHistory(raw);\n    } catch {\n      // best effort\n    } finally {\n      this.hydrated = true;\n      this.notify();\n    }\n  }\n\n  private async persistShowHistory(): Promise<void> {\n    if (!this.options.persistence) return;\n    try {\n      const json = this.core.surveyShowHistoryJson();\n      await this.options.persistence.write(SHOW_HISTORY_STORAGE_KEY, json);\n    } catch {\n      // best effort\n    }\n  }\n\n  private notify(): void {\n    if (this.listeners.size === 0) return;\n    const actives = this.getActive();\n    for (const l of this.listeners) {\n      try {\n        l(actives);\n      } catch {\n        // ignore listener errors\n      }\n    }\n  }\n}\n\nexport type { SurveyDefinition, SurveyResponse, SurveyTargetingContext } from '@layers/core-wasm';\n","// @layers/react-native — React Native SDK.\n// Thin wrapper over @layers/core-wasm. Owns only:\n//   - AsyncStorage persistence backend\n//   - React Native device info (Platform, Dimensions)\n//   - SKAN module (iOS-only conversion value management)\n//   - ATT module (iOS-only App Tracking Transparency)\n//   - Deep links via React Native Linking API\n//   - AppState listener for background/foreground flush\n//   - NetInfo for online/offline detection\nimport {\n  FetchHttpClient,\n  LayersCore,\n  LayersError,\n  MemoryPersistence,\n  initWasm\n} from '@layers/core-wasm';\nimport type {\n  BeforeSendHook,\n  ConsentState,\n  DeviceContext,\n  Environment,\n  EventProperties,\n  FeatureFlagBootstrap,\n  FeatureFlagValue,\n  FeatureFlagsListener,\n  GroupsState,\n  PersistenceBackend,\n  Platform as PlatformType,\n  UserProperties\n} from '@layers/core-wasm';\n\nimport {\n  getAdvertisingId,\n  getVendorId,\n  requestTrackingAuthorization as requestATT\n} from './att.js';\nimport type { ATTStatus } from './att.js';\nimport { installExceptionAutoCapture } from './exceptions.js';\nimport { SKANManager as SKANManagerImpl, setSkanCore } from './skan.js';\n\nexport type {\n  BeforeSendEvent,\n  BeforeSendHook,\n  ConsentState,\n  DeviceContext,\n  EcommerceItem,\n  Environment,\n  EventProperties,\n  GroupsState,\n  UserProperties,\n  // Tier 4 — feature flags\n  FeatureFlagValue,\n  FeatureFlagBootstrap,\n  FeatureFlagBootstrapData,\n  FeatureFlagsListener,\n  FeatureFlagDefinition,\n  FeatureFlagCondition,\n  FeatureFlagVariant\n} from '@layers/core-wasm';\n\nexport { LayersError } from '@layers/core-wasm';\n\n// Tier 1: typed event constants + builders (renamed `build*` to avoid\n// collision with the legacy single-arg helpers exported below).\nexport { LayersEvent } from '@layers/core-wasm';\nexport type {\n  LayersEventName,\n  TypedEventPayload,\n  PurchaseEventParams,\n  AddToCartEventParams,\n  BeginCheckoutEventParams,\n  ViewItemEventParams,\n  RefundEventParams,\n  SubscribeEventParams,\n  StartTrialEventParams\n} from '@layers/core-wasm';\nexport {\n  purchaseEvent as buildPurchaseEvent,\n  addToCartEvent as buildAddToCartEvent,\n  beginCheckoutEvent as buildBeginCheckoutEvent,\n  viewItemEvent as buildViewItemEvent,\n  refundEvent as buildRefundEvent,\n  subscribeEvent as buildSubscribeEvent,\n  startTrialEvent as buildStartTrialEvent,\n  onboardingStartEvent as buildOnboardingStartEvent,\n  onboardingCompleteEvent as buildOnboardingCompleteEvent,\n  paywallShowEvent as buildPaywallShowEvent\n} from '@layers/core-wasm';\n\nexport type { SKANConversionRule, SKANPresetConfig, SKANMetrics } from './skan.js';\nexport { SKANManager } from './skan.js';\nexport type { ATTStatus } from './att.js';\nexport {\n  getATTStatus,\n  requestTrackingAuthorization,\n  isATTAvailable,\n  getAdvertisingId,\n  getVendorId\n} from './att.js';\n\n/**\n * Partial device-context update accepted by {@link LayersReactNative.setDeviceInfo}.\n * Omitted fields are preserved; an explicitly supplied `undefined` clears the\n * field from the stored context.\n */\nexport type DeviceInfoUpdate = {\n  [Key in keyof DeviceContext]?: Exclude<DeviceContext[Key], undefined> | undefined;\n};\n\nexport { StandardEvents } from './standard-events.js';\nexport type { StandardEventName, StandardEventPayload } from './standard-events.js';\nexport {\n  loginEvent,\n  signUpEvent,\n  registerEvent,\n  purchaseEvent,\n  addToCartEvent,\n  addToWishlistEvent,\n  initiateCheckoutEvent,\n  startTrialEvent,\n  subscribeEvent,\n  levelStartEvent,\n  levelCompleteEvent,\n  tutorialCompleteEvent,\n  searchEvent,\n  viewItemEvent,\n  viewContentEvent,\n  shareEvent,\n  screenViewEvent,\n  onboardingStartEvent,\n  onboardingCompleteEvent,\n  paywallShowEvent\n} from './standard-events.js';\n\nexport type { SuperwallPaywallInfo, SuperwallEventInfo, SuperwallProduct } from './superwall.js';\nexport {\n  superwallOnEvent,\n  superwallTrackPresentation,\n  superwallTrackDismiss,\n  superwallTrackPurchase,\n  superwallTrackSkip,\n  superwallUserAttributes\n} from './superwall.js';\n\nexport type {\n  CommerceTracker,\n  PurchaseParams,\n  SubscriptionParams,\n  CartItem,\n  OrderParams,\n  RefundParams,\n  PurchaseFailedParams\n} from './commerce.js';\nexport {\n  trackPurchase,\n  trackPurchaseFailed,\n  trackSubscription,\n  trackOrder,\n  trackAddToCart,\n  trackRemoveFromCart,\n  trackBeginCheckout,\n  trackViewProduct,\n  trackRefund\n} from './commerce.js';\n\nexport type { RevenueCatCustomerInfo, RevenueCatPackage, RevenueCatConfig } from './revenuecat.js';\nexport {\n  connectRevenueCat,\n  trackRevenueCatPurchase,\n  syncRevenueCatAttributes,\n  isRevenueCatConnected,\n  resetRevenueCatForTesting\n} from './revenuecat.js';\n\nexport type { BackgroundFlushResult } from './background-flush.js';\nexport {\n  BACKGROUND_FLUSH_TASK_NAME,\n  BACKGROUND_FLUSH_MIN_INTERVAL_MINUTES,\n  createBackgroundFlushHandler,\n  registerExpoBackgroundFlush\n} from './background-flush.js';\n\n// Tier 2: React Navigation auto-capture for $screen_view\nexport {\n  useLayersNavigationTracking,\n  createNavigationListener,\n  resolveActiveRoute\n} from './navigation-tracking.js';\nexport type { NavigationTrackingHooks } from './navigation-tracking.js';\n\n// Tier 5: uncaught JS error auto-capture for $exception\nexport { installExceptionAutoCapture, buildExceptionProperties } from './exceptions.js';\nexport type { ExceptionProperties, ExceptionSink, ExceptionInstallOptions } from './exceptions.js';\n// Tier 8 — Surveys & in-product messaging\nexport { SurveysModule } from './surveys.js';\nexport type { SurveysModuleOptions } from './surveys.js';\nexport type {\n  SurveyAnswer,\n  SurveyDefinition,\n  SurveyDisplay,\n  SurveyPosition,\n  SurveyQuestion,\n  SurveyResponse,\n  SurveyTargeting,\n  SurveyTargetingContext,\n  SurveyType\n} from '@layers/core-wasm';\n\nexport interface LayersRNConfig {\n  appId: string;\n  environment: Environment;\n  appUserId?: string;\n  /** Verbose console logging. @default false */\n  enableDebug?: boolean;\n  /**\n   * Enable per-device DebugView. The SDK generates and persists a stable\n   * `debug_token` (UUID via AsyncStorage) and sends `X-Debug-Token: <uuid>`\n   * on every event upload. Use {@link LayersReactNative.getDebugToken}\n   * to retrieve the token for displaying in dev UIs.\n   * @default false\n   */\n  debug?: boolean;\n  baseUrl?: string;\n  flushIntervalMs?: number;\n  flushThreshold?: number;\n  maxQueueSize?: number;\n  /**\n   * Whether to automatically fire an `app_open` event during init().\n   * Set to `false` if you want to fire the event manually.\n   * @default true\n   */\n  autoTrackAppOpen?: boolean;\n  /**\n   * Whether to automatically track `deep_link_opened` events when a deep link\n   * is received. The tracked event includes the parsed URL components and all\n   * query parameters (UTM params, click IDs like fbclid/gclid/ttclid, etc.)\n   * as flat top-level properties.\n   *\n   * This listener runs in addition to any consumer-registered listener via\n   * `setupDeepLinkListener()` -- it does not replace it.\n   *\n   * @default true\n   */\n  autoTrackDeepLinks?: boolean;\n  /**\n   * Whether to install a React Native global error handler\n   * (`ErrorUtils.setGlobalHandler`) that turns uncaught JS errors into\n   * `$exception` events. The handler that was installed before still runs\n   * afterwards, so RedBox in development and the native crash in release are\n   * unchanged. Disable if another crash reporter owns the global handler and\n   * you do not want both reporting.\n   *\n   * @default true\n   */\n  autoTrackExceptions?: boolean;\n  /**\n   * Whether to automatically emit `$app_background` / `$app_foreground`\n   * lifecycle events on AppState transitions. The flush trigger on\n   * background remains active regardless of this flag — only the EVENT\n   * emission is gated.\n   *\n   * @default true\n   */\n  autoTrackAppLifecycle?: boolean;\n  /**\n   * Whether to emit `$first_open` on the very first launch for a given\n   * install (gated by AsyncStorage). Subsequent launches do not emit it.\n   *\n   * Note: the Rust core emits `$first_open` on first init via the\n   * super-properties / install-id machinery. Setting this to `false`\n   * suppresses any *additional* emission from the wrapper layer; it\n   * does not unwind events emitted by the core.\n   *\n   * @default true\n   */\n  autoTrackFirstOpen?: boolean;\n  /**\n   * Whether to emit `$app_update` on launch when the persisted app\n   * version differs from the current `device_context.app_version`.\n   *\n   * @default true\n   */\n  autoTrackAppUpdate?: boolean;\n  /**\n   * Tier 4: optional bootstrap data for feature flags. Pre-seed flag values\n   * so the first render after launch doesn't flicker from default-off to\n   * actual-value while the first /config fetch is in flight.\n   */\n  bootstrap?: FeatureFlagBootstrap;\n  /**\n   * Your app's `AppState`, passed in explicitly.\n   *\n   * Only needed when your bundler hands the SDK a **second copy** of\n   * `react-native`. React Native delivers native events (`appStateDidChange`\n   * among them) to exactly one copy — the one whose `RCTDeviceEventEmitter`\n   * is registered as a callable module, which is the copy your app's entry\n   * file imported. A listener registered from any other copy sits on a dead\n   * emitter: `addEventListener` returns a normal subscription object and it\n   * simply never fires. The SDK detects that situation and reports it (see\n   * the error message), and this is the escape hatch that fixes it without\n   * touching your bundler:\n   *\n   * ```ts\n   * import { AppState } from 'react-native';\n   * new LayersReactNative({ appId, environment, appState: AppState });\n   * ```\n   *\n   * @default the SDK's own `require('react-native').AppState`\n   */\n  appState?: AppStateLike;\n}\n\n/**\n * The slice of React Native's `AppState` the SDK uses.\n *\n * Structural, so a host can pass the real `AppState` (or a stand-in) without\n * the SDK depending on React Native's types.\n */\nexport interface AppStateLike {\n  addEventListener(\n    type: 'change',\n    handler: (state: string) => void\n  ): { remove: () => void } | undefined;\n}\n\n/**\n * The surface the SDK reads off the `react-native` module.\n */\ninterface ReactNativeLike {\n  AppState?: AppStateLike;\n  AppRegistry?: { getAppKeys?: () => unknown };\n}\n\n/**\n * The surface `AppRegistry.js` registers at module scope in EVERY copy of\n * react-native, duplicate or not, so it proves nothing on its own.\n */\nconst LOGBOX_SURFACE_KEY = 'LogBox';\n\n/**\n * Is this the copy of react-native the host app actually runs?\n *\n * `AppRegistry.registerComponent` is called by the app's entry file, so the\n * live copy carries the app's own surface key alongside LogBox. A copy that\n * only the SDK imported was never handed an application and carries LogBox\n * alone.\n *\n * Returns `null` when the question cannot be answered (no AppRegistry, a\n * shape we don't recognise). Callers must stay silent on `null` — inventing a\n * diagnosis from missing evidence is worse than the silence this replaces.\n */\nfunction hostAppIsRegisteredOn(rn: unknown): boolean | null {\n  try {\n    const keys = (rn as ReactNativeLike | null)?.AppRegistry?.getAppKeys?.();\n    if (!Array.isArray(keys)) return null;\n    return keys.some((key) => typeof key === 'string' && key !== LOGBOX_SURFACE_KEY);\n  } catch {\n    return null;\n  }\n}\n\nconst DUPLICATE_REACT_NATIVE_MESSAGE =\n  '[Layers] The SDK resolved a SECOND copy of react-native, not the one your app runs. ' +\n  'React Native delivers native lifecycle events to a single copy, so this listener can ' +\n  'never fire: $app_background and $app_foreground will not be emitted and the background ' +\n  'crash-safety snapshot will not run, which loses every event queued since the last flush ' +\n  'on any launch that ends in a kill. Fix the duplicate in your bundler — in a Metro ' +\n  'monorepo set `resolver.disableHierarchicalLookup = true` and list every workspace ' +\n  \"node_modules in `resolver.nodeModulesPaths` — or hand the SDK your app's module: \" +\n  \"`new LayersReactNative({ ..., appState: AppState })` with AppState imported from 'react-native'.\";\n\ndeclare const __LAYERS_RN_VERSION__: string;\n// React Native's bundler defines `__DEV__` and Metro inlines the identifier\n// in many configurations, so read the identifier itself rather than a\n// property on `globalThis`.\ndeclare const __DEV__: boolean | undefined;\nconst SDK_VERSION: string =\n  typeof __LAYERS_RN_VERSION__ !== 'undefined' ? __LAYERS_RN_VERSION__ : '0.1.0';\n\nexport type ErrorListener = (error: Error) => void;\n\n/**\n * Listener for SDK initialization timing metrics.\n *\n * @param mainThreadDurationMs Time spent in the synchronous portion of init\n *   (core creation, device info collection, before background work).\n * @param totalDurationMs Total wall-clock time of the `init()` call,\n *   including all async work (remote config fetch, attribution signals, app_open event).\n */\nexport type InitListener = (mainThreadDurationMs: number, totalDurationMs: number) => void;\n\nexport class LayersReactNative {\n  private core!: LayersCore;\n  // Backing store of the pre-init eager core; init() migrates its drained\n  // events into the durable AsyncStorage backend (see init()).\n  private readonly eagerPersistence: MemoryPersistence;\n  private appUserId: string | undefined;\n  private isOnline = true;\n  private readonly enableDebug: boolean;\n  private appStateSubscription: { remove(): void } | null = null;\n  private exceptionUninstall: (() => void) | null = null;\n  // Resolves once every AsyncStorage write the persistence backend has\n  // started has landed. Set with the durable backend in init(); the fatal\n  // `$exception` path waits on it before the process is allowed to die.\n  private persistenceSettle: (() => Promise<void>) | null = null;\n  // Deferred \"can this listener ever fire?\" probe — see\n  // scheduleLifecycleReachabilityCheck.\n  private _lifecycleCheckTimer: ReturnType<typeof setTimeout> | null = null;\n  private netInfoUnsubscribe: (() => void) | null = null;\n  private readonly config: LayersRNConfig;\n\n  // set-user-once semantics: once appUserId is set, it cannot be changed\n  // (must clearAppUserId first)\n  private userIdLocked = false;\n\n  // Error listeners\n  private readonly errorListeners: Set<ErrorListener> = new Set();\n\n  // SKAN facade over the Rust core (iOS only). The core owns rule evaluation,\n  // presets, and the monotonic floor; this is a thin delegating wrapper.\n  private _skanManager: SKANManagerImpl | null = null;\n  // Arm Apple's postback window once per launch (reset on shutdown so a re-init\n  // re-arms a fresh window).\n  private _skanArmed = false;\n\n  // Internal deep link listener unsubscribe (auto-tracking)\n  private deepLinkUnsubscribe: (() => void) | null = null;\n\n  // Recent events buffer for debug overlay (circular, last N events)\n  private static readonly MAX_RECENT_EVENTS = 20;\n  private _recentEvents: string[] = [];\n  private _isInitialized = false;\n\n  // --- init() lifecycle guard ---\n  //\n  // `_initPromise` makes init() idempotent: concurrent callers await the same\n  // in-flight run and a completed init returns immediately. Without it a\n  // second init() re-registered the AppState, NetInfo and deep-link listeners\n  // on top of the live ones (nothing removed the old handles) and re-emitted\n  // `app_open`, so two init() calls produced app_install, app_open, app_open.\n  //\n  // `_initGeneration` is bumped by shutdown(). Every await point in runInit()\n  // re-checks it, so a shutdown() that lands mid-init aborts the run instead\n  // of finishing on an instance nobody holds. See isRunAbandoned().\n  private _initPromise: Promise<void> | null = null;\n  private _initGeneration = 0;\n\n  // The generation that installed the runtime currently hanging off `this` —\n  // the live core, the AppState/NetInfo/deep-link handles, the config-poll\n  // timer. Stamped at each install site so an abandoned run can tell its own\n  // resources from ones a NEWER init() installed after it lost the race.\n  // Without it, an abandoned run's cleanup killed the listeners and core of the\n  // run that replaced it, leaving a permanently dead SDK that still reported\n  // `isInitialized === true`.\n  private _runtimeGeneration = 0;\n\n  // Remote config polling timer (300s interval, matches Swift/Kotlin/Flutter/Unity SDKs)\n  private _configPollTimer: ReturnType<typeof setInterval> | null = null;\n  private static readonly CONFIG_POLL_INTERVAL_MS = 300_000; // 5 minutes\n\n  // Cached AdServices attribution token (iOS only)\n  private _adServicesToken: string | null = null;\n\n  // Cached install referrer data (Android only)\n  private _installReferrer: InstallReferrerData | null = null;\n\n  // Parsed attribution params from the install referrer query string (Android).\n  // Captured in initializeDeviceInfo so app_open / app_install can carry every\n  // recognized click ID + UTM, matching the Flutter/Kotlin behavior (they add\n  // all parsed params to the event properties, not just the 4 setAttributionData\n  // fields). Without this, `click_id`, `gbraid`, `wbraid`, `rclid`, `li_fat_id`,\n  // `sclid`, and the UTM params are silently discarded after parsing.\n  private _installReferrerParams: Record<string, string> | null = null;\n\n  // Whether the SDK had prior state (install_id existed) at initialization\n  // time. When false, install event gating applies.\n  private _hadPriorSdkState = false;\n\n  // Attribution data stored for attachment to subsequent events.\n  private _attributionDeeplinkId: string | null = null;\n  private _attributionGclid: string | null = null;\n  private _attributionFbclid: string | null = null;\n  private _attributionFbc: string | null = null;\n  private _attributionTtclid: string | null = null;\n  private _attributionMsclkid: string | null = null;\n\n  private static readonly ATTRIBUTION_DEEPLINK_ID_KEY = '@layers/deeplink_id';\n  private static readonly ATTRIBUTION_GCLID_KEY = '@layers/gclid';\n  private static readonly ATTRIBUTION_FBCLID_KEY = '@layers/fbclid';\n  private static readonly ATTRIBUTION_FBC_KEY = '@layers/fbc';\n  private static readonly ATTRIBUTION_TTCLID_KEY = '@layers/ttclid';\n  private static readonly ATTRIBUTION_MSCLKID_KEY = '@layers/msclkid';\n\n  // Init timing listener\n  private _initListener: InitListener | null = null;\n\n  constructor(config: LayersRNConfig) {\n    this.enableDebug = config.enableDebug ?? false;\n    this.appUserId = config.appUserId;\n    this.config = config;\n\n    // Create the core eagerly with an empty MemoryPersistence so that\n    // track()/screen()/etc. work immediately (before init() is called).\n    // init() will replace this with a fully-hydrated AsyncStorage backend.\n    //\n    // We deliberately do NOT pass `debug: true` to the eager core: the debug\n    // token is meant to be a stable per-install UUID, but the eager core\n    // would mint one in MemoryPersistence that is then thrown away on init().\n    // The next launch would mint a different ephemeral one. Defer all debug\n    // token work to the AsyncStorage-backed post-init core; pre-init events\n    // are queued without an X-Debug-Token and flushed by the post-init core,\n    // which adds the stable header at request time.\n    const httpClient = new FetchHttpClient();\n    // Kept as a field: init() shuts the eager core down, which drains any\n    // pre-init events into this store — init() must copy them into the\n    // durable AsyncStorage backend before discarding it.\n    this.eagerPersistence = new MemoryPersistence();\n    this.core = LayersCore.init({\n      config: {\n        appId: config.appId,\n        environment: config.environment,\n        ...(config.baseUrl != null && { baseUrl: config.baseUrl }),\n        ...(config.enableDebug != null && { enableDebug: config.enableDebug }),\n        ...(config.flushIntervalMs != null && { flushIntervalMs: config.flushIntervalMs }),\n        ...(config.flushThreshold != null && { flushThreshold: config.flushThreshold }),\n        ...(config.maxQueueSize != null && { maxQueueSize: config.maxQueueSize }),\n        sdkVersion: `react-native/${SDK_VERSION}`,\n        ...(config.bootstrap != null && { bootstrap: config.bootstrap })\n      },\n      httpClient,\n      persistence: this.eagerPersistence\n    });\n    // Wire the SKAN facade to this core so process_event/config/floor all run in\n    // the Rust engine (single source of truth), not a parallel JS engine.\n    setSkanCore(this.core);\n\n    // Stamp the eager core with everything the wrapper can read synchronously\n    // about the device. Pre-init events used to ship as `platform: \"web\"` —\n    // buildEvent's pre-context default — and the wrong platform was baked into\n    // the persisted event, so the migration into the post-init core\n    // re-hydrated it as-is. The wrapper knows it is React Native before init()\n    // is ever called, so a `web` install was never justified. init()'s\n    // initializeDeviceInfo() overwrites this with the complete context (device\n    // model, app version, IDFV/GAID, install ID) once the async native reads\n    // finish.\n    this.applyEagerDeviceContext();\n\n    if (this.appUserId) {\n      this.core.identify(this.appUserId);\n      this.userIdLocked = true;\n    }\n  }\n\n  /**\n   * Best-effort device context available at construction time — no `await`,\n   * so it can run in the constructor before any pre-init event is tracked.\n   * Only the synchronously readable fields; the rest arrive with\n   * initializeDeviceInfo().\n   */\n  private applyEagerDeviceContext(): void {\n    // Same fallback as initializeDeviceInfo(): even with no React Native APIs\n    // reachable, this is the React Native SDK, and 'react-native' is a true\n    // platform value. 'web' was always wrong here.\n    const context: DeviceContext = {\n      platform: 'react-native',\n      osVersion: 'unknown',\n      deviceModel: 'unknown',\n      locale: 'en-US'\n    };\n\n    try {\n      const { Platform, Dimensions } = require('react-native');\n      context.platform = (\n        Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'react-native'\n      ) as PlatformType;\n      context.osVersion =\n        typeof Platform.Version === 'string' ? Platform.Version : String(Platform.Version);\n      context.locale = getLocale();\n      // Android exposes the model through Platform.constants synchronously;\n      // iOS needs a native round-trip, so it waits for initializeDeviceInfo().\n      if (Platform.OS === 'android') {\n        const brand = Platform.constants?.Brand ?? '';\n        const model = Platform.constants?.Model ?? '';\n        const deviceModel = brand && model ? `${brand} ${model}` : model || brand;\n        if (deviceModel) context.deviceModel = deviceModel;\n      }\n      try {\n        const window = Dimensions.get('window');\n        context.screenSize = `${Math.round(window.width)}x${Math.round(window.height)}`;\n      } catch {\n        // Dimensions unavailable — non-fatal\n      }\n      try {\n        context.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n      } catch {\n        // Intl unavailable — non-fatal\n      }\n    } catch {\n      // React Native APIs not available (e.g. in tests) — ship the fallback.\n    }\n\n    try {\n      this.core.setDeviceContext(context);\n    } catch {\n      // Never let device-context stamping break construction.\n    }\n  }\n\n  /**\n   * The platform this instance runs on: 'ios' | 'android' | 'react-native'.\n   * Reads the device context the constructor stamped (applyEagerDeviceContext)\n   * and falls back to a direct `Platform.OS` read, so it answers correctly\n   * before init() has completed.\n   */\n  private currentPlatform(): string {\n    try {\n      const platform = (this.core.getDeviceContext() as { platform?: string }).platform;\n      if (platform) return platform;\n    } catch {\n      // Fall through to the direct read.\n    }\n    try {\n      const { Platform } = require('react-native');\n      return String(Platform.OS);\n    } catch {\n      return 'react-native';\n    }\n  }\n\n  /**\n   * Initialize the SDK. Idempotent: concurrent calls await the same in-flight\n   * run, and a completed init() returns immediately. shutdown() releases the\n   * latch, so shutdown() → init() re-initializes.\n   */\n  async init(): Promise<void> {\n    if (this._initPromise) return this._initPromise;\n\n    const promise = this.runInit(this._initGeneration);\n    this._initPromise = promise;\n    try {\n      await promise;\n    } catch (e) {\n      // A rejected init must not poison every later attempt — drop the cached\n      // promise so a retry can run.\n      if (this._initPromise === promise) this._initPromise = null;\n      throw e;\n    }\n  }\n\n  /**\n   * True when a shutdown() landed after this init() run started. The caller\n   * must return immediately.\n   *\n   * Without this gate init() ran to completion on an abandoned instance: it\n   * built a second live core (the AsyncStorage rebuild below), installed the\n   * AppState/NetInfo/deep-link listeners and the 300s config-poll timer on it,\n   * and tracked a duplicate `app_open` — one orphaned SDK, with its own\n   * timers, per remount. React 18 StrictMode mounts → unmounts → remounts\n   * every effect, so the Expo `LayersProvider` (which calls shutdown() in\n   * cleanup while init() is still awaiting) hit this on every dev launch.\n   *\n   * **This check has no side effects, deliberately.** It used to tear the\n   * runtime down on any generation mismatch, which is wrong the moment two\n   * runs overlap: init A → shutdown → init B completes → A finally reaches a\n   * checkpoint and demolishes *B's* listeners, core and timer, leaving an SDK\n   * that reported `isInitialized === true` and delivered nothing. Cleanup\n   * belongs to shutdown() (which owns everything installed at the moment it\n   * runs) and to {@link abandonRun} (which proves ownership by generation\n   * first).\n   */\n  private isRunAbandoned(generation: number): boolean {\n    return generation !== this._initGeneration;\n  }\n\n  /**\n   * Abandon this run and release only the resources it installed itself.\n   *\n   * Every install site below is *synchronously* adjacent to its preceding\n   * checkpoint — no `await` sits between \"still current?\" and the assignment —\n   * so a shutdown() can never slip in between the two, and anything this run\n   * did install was already torn down by that shutdown(). The ownership check\n   * is what keeps that true if a future edit ever puts an await in between:\n   * the run cleans up only while it is still the runtime's owner.\n   */\n  private abandonRun(generation: number): void {\n    if (this._runtimeGeneration !== generation) return;\n    this.teardownRuntime();\n  }\n\n  /**\n   * Checkpoint used at every await boundary in runInit(): report whether this\n   * run has been abandoned and, if so, release only what it still owns.\n   */\n  private initAborted(generation: number): boolean {\n    if (!this.isRunAbandoned(generation)) return false;\n    this.abandonRun(generation);\n    return true;\n  }\n\n  private async runInit(generation: number): Promise<void> {\n    const initStartTime = Date.now();\n\n    // Opportunistic WASM: Hermes gained WebAssembly support in RN 0.84\n    // (Hermes V1, Feb 2026). Where it exists, load the real Rust core so\n    // the post-init rebuild below binds to it. On older Hermes there is no\n    // WebAssembly global — the (supported) JS fallback engine is the\n    // production engine there. Note initWasm() swallows load failures\n    // internally; isWasmReady() after the rebuild is the truthful signal\n    // (see the warning below the rebuild).\n    if (typeof WebAssembly !== 'undefined') {\n      await initWasm();\n    }\n    if (this.initAborted(generation)) return;\n\n    // Re-create the core with a pre-loaded AsyncStorage persistence backend\n    // so that events persisted in a prior session are properly rehydrated\n    // (and, post-initWasm, so the rebuilt core binds the real Rust engine).\n    try {\n      const { backend: persistence, settle: persistenceSettle } =\n        await createAsyncStoragePersistence(this.config.appId);\n      // Checked before the swap. Building the replacement core after\n      // shutdown() has already run is exactly how the orphan was created.\n      if (this.initAborted(generation)) return;\n      this.persistenceSettle = persistenceSettle;\n      const httpClient = new FetchHttpClient();\n      // Shut down the eager core before replacing it. shutdown() drains any\n      // pre-init events into the eager MemoryPersistence — copy them into\n      // the durable backend (under fresh keys, so a prior session's\n      // events_snapshot is never overwritten) BEFORE constructing the new\n      // core, whose hydration pass then re-queues them. They used to be\n      // garbage-collected with the eager store (audit H5).\n      // Carry in-memory state (consent, super properties, groups, device\n      // context) across the swap — pre-init setConsent etc. used to be\n      // silently dropped by the rebuild.\n      const migrationState = this.core.exportMigrationState();\n      this.core.shutdown();\n      try {\n        let migrated = 0;\n        for (const key of this.eagerPersistence.listKeys('events_')) {\n          const data = this.eagerPersistence.read(key);\n          if (data && data.length > 0) {\n            persistence.write(`events_preinit_${String(migrated).padStart(3, '0')}`, data);\n            migrated += 1;\n          }\n          // Consume the eager record either way — a second init() call must\n          // not re-migrate (and re-deliver) the same pre-init events.\n          this.eagerPersistence.delete(key);\n        }\n      } catch {\n        // Best effort — worst case pre-init events are dropped, never\n        // duplicated.\n      }\n      this.core = LayersCore.init({\n        config: {\n          appId: this.config.appId,\n          environment: this.config.environment,\n          ...(this.config.baseUrl != null && { baseUrl: this.config.baseUrl }),\n          ...(this.config.enableDebug != null && { enableDebug: this.config.enableDebug }),\n          ...(this.config.debug != null && { debug: this.config.debug }),\n          ...(this.config.flushIntervalMs != null && {\n            flushIntervalMs: this.config.flushIntervalMs\n          }),\n          ...(this.config.flushThreshold != null && {\n            flushThreshold: this.config.flushThreshold\n          }),\n          ...(this.config.maxQueueSize != null && { maxQueueSize: this.config.maxQueueSize }),\n          sdkVersion: `react-native/${SDK_VERSION}`,\n          ...(this.config.bootstrap != null && { bootstrap: this.config.bootstrap })\n        },\n        httpClient,\n        persistence,\n        // Handed over AT INIT, not through a follow-up applyMigrationState().\n        // The eager core minted this launch's identity into a MemoryPersistence\n        // that never reaches disk; the durable core, left to itself, mints a\n        // SECOND one and writes it before this hand-off can replace it. Two\n        // writes to `identity_state` in one tick, and AsyncStorage v3 does not\n        // order writes — v3 replaced v1/v2's `SerialExecutor` with one\n        // unsynchronised coroutine per call. When the mint's write won, the\n        // install's durable record named an identity that had never been on the\n        // wire, the next launch resumed it, and the install split in two: two\n        // `anonymous_id`s, two `device_id`s, two `$first_open`s, with launch\n        // one's events orphaned under an identity that never recurs. Measured\n        // on 24% of offline-first installs with async-storage 3.1.1, 0/18 with\n        // 2.2.0.\n        migration: migrationState\n      });\n      // This run now owns the live core (see `_runtimeGeneration`).\n      this._runtimeGeneration = generation;\n      // Re-point the SKAN facade at the (re-created) core.\n      setSkanCore(this.core);\n\n      if (this.appUserId) {\n        this.core.identify(this.appUserId);\n      }\n    } catch {\n      // AsyncStorage pre-load failed; keep using the eager core with MemoryPersistence\n    }\n\n    // Truthful fallback signal: initWasm() swallows load failures, so the\n    // NO WASM-failure warning here, deliberately. Under Metro this package\n    // resolves @layers/core-wasm's react-native entry, whose initWasm() is a\n    // guaranteed no-op — the wasm binary is not in the bundle and never can\n    // be, so `WebAssembly exists && !isWasmActive()` is simply \"modern\n    // Hermes/JSC/debugger attached\", true on every RN 0.84+ launch. The old\n    // warning here told every such customer to go fix Metro asset handling\n    // for a file we intentionally do not ship. The JS engine IS the\n    // supported production engine on React Native.\n\n    // Restore persisted attribution data BEFORE initializeDeviceInfo. The\n    // install-referrer parser inside initializeDeviceInfo calls\n    // setAttributionData and merges with this._attribution* fields, so those\n    // fields need the prior-session values populated first — otherwise the\n    // merge clobbers AsyncStorage with nulls for any click ID the referrer\n    // didn't parse. The original invariant (\"restored before events are\n    // tracked\") is still satisfied because initializeDeviceInfo does not\n    // emit events.\n    await this.restoreAttributionData();\n\n    await this.initializeDeviceInfo();\n    if (this.initAborted(generation)) return;\n\n    // Listener + timer installs are all synchronous from here to the next\n    // checkpoint, so the ownership stamp and the resources stay in step.\n    this._runtimeGeneration = generation;\n    this.setupAppStateListener();\n    this.setupNetInfoListener();\n\n    // Main thread init complete — record timing\n    const mainThreadDurationMs = Date.now() - initStartTime;\n\n    // Remote config fetch is best-effort. The JS layer fetches the config\n    // endpoint; when the WASM/Rust core is loaded, it applies sampling,\n    // rate limits, and filtering internally. The JS fallback path does not\n    // currently parse/apply the config response — a future enhancement\n    // will add JS-side config application for non-WASM environments.\n    await this.core.fetchRemoteConfig().catch(() => {});\n\n    // Auto-configure SKAN from remote config (iOS only).\n    // The server's remote config `skan` section drives preset/rules so consumers\n    // don't need to call SKANManager.setPreset() manually.\n    await this.configureSkanFromRemoteConfig();\n    if (this.initAborted(generation)) return;\n\n    // Start periodic remote config polling (every 300s).\n    // Matches Swift, Kotlin, Flutter, and Unity SDKs.\n    this.startConfigPolling();\n\n    // Read the server-driven privacy switches ONCE, here, after the config\n    // fetch above has had its chance to land. Both default to false and both\n    // treat \"no config\" as false — `getRemoteConfigJson()` returns null until a\n    // fetch succeeds, so a launch whose fetch failed leaves every switch off\n    // rather than guessing. Read outside the iOS branch below because the\n    // fingerprint switch gates an Android path too.\n    let clipboardEnabled = false;\n    let fingerprintResolveEnabled = false;\n    try {\n      const configJson = this.core.getRemoteConfigJson?.();\n      if (configJson) {\n        const config = JSON.parse(configJson);\n        clipboardEnabled = config?.clipboard_attribution_enabled === true;\n        // `=== true` on purpose: `\"true\"`, `1` and every other truthy\n        // non-boolean leave the gate shut.\n        fingerprintResolveEnabled = config?.fingerprint_resolve_enabled === true;\n      }\n    } catch {}\n\n    // Clipboard attribution: on first iOS launch, check clipboard for Layers click URL\n    let clipboardData: ClipboardAttribution | null = null;\n    try {\n      const { Platform } = require('react-native');\n      if (Platform.OS === 'ios' && clipboardEnabled) {\n        clipboardData = await readClipboardAttribution();\n      }\n    } catch {}\n\n    // Last gate before the SDK emits anything: a shutdown() that landed while\n    // the clipboard/config awaits were pending must not produce a lifecycle\n    // event from an abandoned instance.\n    if (this.initAborted(generation)) return;\n\n    // Canonical `install_referrer` event (Android). Deliberately outside the\n    // `autoTrackAppOpen` branch below: Kotlin and Unity emit it from the\n    // referrer callback, independent of any lifecycle event, and an app that\n    // opts out of automatic `app_open` still needs its install attribution.\n    await this.emitInstallReferrerEvent();\n    if (this.initAborted(generation)) return;\n\n    // Auto-track app_open event with attribution signals and first launch detection\n    if (this.config.autoTrackAppOpen !== false) {\n      const appOpenProps: Record<string, unknown> = {};\n\n      // First launch detection with install event gating (24-hour window).\n      // The caller reads and persists the flag; shouldTreatAsNewInstall is pure.\n      let isFirstLaunchByFlag = true;\n      try {\n        const AsyncStorage = await loadAsyncStorage();\n        if (AsyncStorage) {\n          const scopedKey = firstLaunchTrackedKey(this.config.appId);\n          let flag = await AsyncStorage.getItem(scopedKey);\n          let adoptLegacy = false;\n\n          if (flag === null) {\n            // One-shot migration for installs written before the scoping\n            // landed: adopt the legacy global flag for THIS appId, then delete\n            // it so the fallback fires at most once per device.\n            //\n            // Deleting means a second appId on the same device will not see\n            // the legacy flag — and that is the correct outcome, the one this\n            // scoping exists for. A different appId is a different install\n            // identity to the engine, which mints fresh identity state and\n            // emits `$first_open`; `app_install` must accompany it, and a flag\n            // another app wrote has no business suppressing it. Leaving the\n            // legacy key in place recreated the staging→production gap.\n            const legacy = await AsyncStorage.getItem(FIRST_LAUNCH_TRACKED_KEY_LEGACY);\n            if (legacy === 'true') {\n              flag = legacy;\n              adoptLegacy = true;\n            }\n          }\n\n          // The READ is the decision — apply it before any write can throw.\n          // With the suppression applied after the migration writes, a storage\n          // failure escaped to the outer catch with `isFirstLaunchByFlag` still\n          // true, and an upgrading device (legacy flag present, so it had\n          // already reported its install) emitted a duplicate `app_install`.\n          if (flag === 'true') isFirstLaunchByFlag = false;\n\n          if (adoptLegacy) {\n            // Best-effort bookkeeping, isolated so it cannot undo the decision\n            // above. A failure here just leaves both keys as they were and the\n            // migration re-runs next launch — self-healing, and suppression\n            // holds throughout because a read of EITHER key suppresses.\n            //\n            // Sequential on purpose: the legacy key is deleted only once the\n            // scoped write has landed. Deleting first would risk losing both\n            // keys to a failed write and re-firing `app_install`.\n            try {\n              await AsyncStorage.setItem(scopedKey, 'true');\n              await AsyncStorage.removeItem(FIRST_LAUNCH_TRACKED_KEY_LEGACY);\n            } catch {\n              // best-effort\n            }\n          }\n        }\n      } catch {\n        // best-effort\n      }\n\n      const isFirstLaunch = await shouldTreatAsNewInstall(\n        isFirstLaunchByFlag,\n        this._hadPriorSdkState,\n        this.enableDebug\n      );\n      appOpenProps.is_first_launch = isFirstLaunch;\n\n      // On a true first launch, if we have no attribution signal yet (iOS has\n      // no Play Install Referrer; Android may not have Google Play Services),\n      // ask the server to resolve us via device fingerprint against recent\n      // /c/:appId clicks. This closes the web-to-app attribution gap for Meta\n      // and TikTok on iOS where there's no alternative. Best-effort — any\n      // failure is swallowed so we never block app_open.\n      //\n      // GATED, DEFAULT OFF (`fingerprint_resolve_enabled`, read above). The\n      // probe sends device model, OS version, locale, timezone and screen size\n      // for the server to match against recent clicks — device fingerprinting\n      // under App Store Review Guideline 5.1.2 regardless of ATT status. An\n      // install whose config fetch has not landed and said `true` must not make\n      // it, which means a first launch with no reachable config forfeits\n      // fingerprint resolution: `isFirstLaunch` is false by the next launch, so\n      // there is no second chance for that install. Losing an attribution match\n      // is the correct trade against fingerprinting an install the server never\n      // asked us to fingerprint.\n      let hasAttribution =\n        this._attributionFbclid != null ||\n        this._attributionGclid != null ||\n        this._attributionTtclid != null ||\n        this._attributionMsclkid != null;\n      if (fingerprintResolveEnabled && isFirstLaunch && !hasAttribution) {\n        try {\n          await this.resolveClickFromFingerprint();\n        } catch {\n          // best-effort\n        }\n      }\n\n      if (clipboardData) {\n        appOpenProps.clipboard_attribution_url = clipboardData.clickUrl;\n        appOpenProps.clipboard_click_id = clipboardData.clickId;\n      }\n      if (this._adServicesToken) {\n        appOpenProps.adservices_token = this._adServicesToken;\n      }\n      // DEPRECATED — removed in 3.2.12.\n      //\n      // React Native used to be the only SDK that reported the Play install\n      // referrer as `install_referrer_*` PROPERTIES on `app_open`, while\n      // Kotlin, Unity and Flutter emit a dedicated `install_referrer` event\n      // with the raw string under `referrer`. As of this release RN emits that\n      // canonical event too (see `emitInstallReferrerEvent`), and these three\n      // keys are kept for exactly one release so server-side consumers still\n      // reading `install_referrer_url` keep their signal while they migrate.\n      //\n      // 3.2.12 deletes this block. The canonical event is the only shape then.\n      if (this._installReferrer) {\n        appOpenProps.install_referrer_url = this._installReferrer.referrerUrl;\n        if (this._installReferrer.referrerClickTimestamp) {\n          appOpenProps.install_referrer_click_timestamp =\n            this._installReferrer.referrerClickTimestamp;\n        }\n        if (this._installReferrer.installBeginTimestamp) {\n          appOpenProps.install_referrer_install_timestamp =\n            this._installReferrer.installBeginTimestamp;\n        }\n      }\n      // Spread every parsed install-referrer param (click IDs + UTM) into the\n      // app_open / app_install event properties. Matches Flutter + Kotlin +\n      // Unity behavior and ensures the server matcher receives click_id,\n      // gbraid, wbraid, rclid, li_fat_id, sclid, etc. — not just the four\n      // click IDs that flow through setAttributionData.\n      if (this._installReferrerParams) {\n        for (const [key, value] of Object.entries(this._installReferrerParams)) {\n          if (appOpenProps[key] === undefined) {\n            appOpenProps[key] = value;\n          }\n        }\n      }\n      // The first-launch resolution above awaits AsyncStorage and (on a true\n      // first launch) a network round-trip — plenty of room for a shutdown().\n      if (this.initAborted(generation)) return;\n\n      // Send app_install before app_open on first launch for CAPI forwarding\n      if (isFirstLaunch) {\n        this.track('app_install', appOpenProps);\n      }\n      this.track('app_open', appOpenProps);\n\n      // Persist the first-launch flag only once the events are out.\n      //\n      // The flag means \"this install already reported itself\", so writing it\n      // ahead of the emit lets a shutdown() landing in that window suppress\n      // `app_install` permanently: the flag says done, no event ever went out,\n      // and every later launch reads the flag and stays silent. Same principle\n      // as the engine's `$first_open` port, which marks the install emitted\n      // only once the event actually reached the queue.\n      //\n      // We do not additionally gate on queue depth: the queue evicts FIFO at\n      // capacity, so a flat depth does not mean the event was refused, and\n      // treating it that way would re-fire `app_install` on every launch of a\n      // backlogged install.\n      //\n      // Only persisted when we actually treated this as a new install. If the\n      // 24-hour install-time gate suppressed it, the flag stays unset so a\n      // future launch can re-evaluate (e.g. after an app update that changes\n      // the gating logic).\n      if (isFirstLaunch) {\n        try {\n          const AsyncStorage = await loadAsyncStorage();\n          if (AsyncStorage) {\n            await AsyncStorage.setItem(firstLaunchTrackedKey(this.config.appId), 'true');\n          }\n        } catch {\n          // best-effort\n        }\n      }\n\n      // App-update detection: compare the persisted app_version against\n      // the current device_context.app_version. Emit `$app_update` when\n      // they differ. On first launch we just persist the version (no\n      // event) so the next launch can compare against it.\n      if (this.config.autoTrackAppUpdate !== false) {\n        try {\n          const ctx = this.core.getDeviceContext();\n          const currentVersion =\n            (ctx as Record<string, unknown>).appVersion ?? appOpenProps.app_version;\n          if (currentVersion && typeof currentVersion === 'string') {\n            const AsyncStorage = await loadAsyncStorage();\n            if (AsyncStorage) {\n              const previousVersion = await AsyncStorage.getItem(LAST_APP_VERSION_KEY);\n              if (previousVersion && previousVersion !== currentVersion) {\n                this.track('$app_update', {\n                  previous_version: previousVersion,\n                  current_version: currentVersion\n                });\n              }\n              await AsyncStorage.setItem(LAST_APP_VERSION_KEY, currentVersion);\n            }\n          }\n        } catch {\n          // best-effort\n        }\n      }\n    }\n\n    if (this.initAborted(generation)) return;\n\n    // Auto-track deep link events (default: enabled).\n    // Sets up an internal deep link listener that fires a `deep_link_opened`\n    // event with the full parsed URL and all query params spread as top-level\n    // properties. This runs in addition to any consumer-registered listener.\n    if (this.config.autoTrackDeepLinks !== false) {\n      this.setupDeepLinkAutoTracking();\n    }\n\n    // Auto-capture uncaught JS errors as `$exception` (default: enabled).\n    if (this.config.autoTrackExceptions !== false) {\n      // A fatal error in a release build ends the process as soon as React\n      // Native's own handler runs, and the persistence backend's AsyncStorage\n      // write is fire-and-forget, so the `$exception` would be gone before it\n      // was durable. `onFatal` makes the handler wait (bounded) for the queue\n      // snapshot and that write first. In development RedBox is that handler;\n      // it runs at once so the error shows without delay.\n      const isDev = typeof __DEV__ !== 'undefined' && __DEV__ === true;\n      this.exceptionUninstall = installExceptionAutoCapture(\n        (eventName, properties) => {\n          this.track(eventName, properties);\n        },\n        {\n          onInternalError: (message, error) => {\n            if (this.enableDebug) console.warn('[Layers]', message, error);\n          },\n          ...(isDev ? {} : { onFatal: () => this.persistForCrash() })\n        }\n      );\n    }\n\n    this._isInitialized = true;\n\n    // Measure total duration (including all async work)\n    const totalDurationMs = Date.now() - initStartTime;\n\n    if (this.enableDebug) {\n      console.log(\n        `[Layers] Init timing: mainThread=${String(mainThreadDurationMs)}ms, total=${String(totalDurationMs)}ms`\n      );\n    }\n\n    // Notify init listener\n    if (this._initListener) {\n      try {\n        this._initListener(mainThreadDurationMs, totalDurationMs);\n      } catch (e) {\n        if (this.enableDebug) {\n          console.warn('[Layers] InitListener threw:', e);\n        }\n      }\n    }\n  }\n\n  track(eventName: string, properties?: EventProperties): void {\n    if (this.enableDebug) {\n      console.log(\n        `[Layers] track(\"${eventName}\", ${Object.keys(properties ?? {}).length} properties)`\n      );\n    }\n    try {\n      const merged = this.mergeAttributionProperties(properties);\n      const depthBefore = this.core.queueDepth();\n      this.core.track(eventName, merged, this.appUserId);\n      const depthAfter = this.core.queueDepth();\n\n      // Queue depth gating: verify event was accepted by core\n      if (depthAfter <= depthBefore && this.enableDebug) {\n        console.warn(\n          `[Layers] track(\"${eventName}\") — event may not have been queued ` +\n            `(depth before=${String(depthBefore)}, after=${String(depthAfter)})`\n        );\n      }\n\n      // Record for debug overlay\n      this.recordRecentEvent(eventName, properties);\n\n      // Forward to the core's SKAN engine (it decides; we apply natively + report\n      // the result). The core owns the monotonic floor and persistence.\n      if (this._skanManager) {\n        void this._skanManager\n          .processEvent(eventName, (merged as Record<string, unknown>) ?? {})\n          .catch(() => {});\n      }\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  screen(screenName: string, properties?: EventProperties): void {\n    this.screenImpl(screenName, properties, true);\n  }\n\n  /**\n   * @internal Entry point for auto-capture integrations (the Expo provider's\n   * router tracker). Identical to `screen()` except the view is kept out of\n   * the SKAN engine: SKAN preset rules key off `screen_name`, and an\n   * auto-captured stream of route views would otherwise change an app's iOS\n   * conversion values the moment auto-capture turned on. Manual `screen()`\n   * calls keep feeding SKAN exactly as before.\n   */\n  _screenAutoCaptured(screenName: string, properties?: EventProperties): void {\n    this.screenImpl(screenName, properties, false);\n  }\n\n  private screenImpl(\n    screenName: string,\n    properties: EventProperties | undefined,\n    forwardToSkan: boolean\n  ): void {\n    if (this.enableDebug) {\n      console.log(\n        `[Layers] screen(\"${screenName}\", ${Object.keys(properties ?? {}).length} properties)`\n      );\n    }\n    try {\n      const merged = this.mergeAttributionProperties(properties);\n      const depthBefore = this.core.queueDepth();\n      this.core.screen(screenName, merged, this.appUserId);\n      const depthAfter = this.core.queueDepth();\n\n      // Queue depth gating: verify event was accepted by core\n      if (depthAfter <= depthBefore && this.enableDebug) {\n        console.warn(\n          `[Layers] screen(\"${screenName}\") — event may not have been queued ` +\n            `(depth before=${String(depthBefore)}, after=${String(depthAfter)})`\n        );\n      }\n\n      this.recordRecentEvent(`screen:${screenName}`, properties);\n\n      // Forward to the core's SKAN engine as a screen_view (preset rules key off\n      // screen_name). We do NOT gate on analytics queue state (sampling / FIFO\n      // eviction don't mean the screen didn't happen), matching the native iOS\n      // wrapper + track(). The core owns the monotonic floor.\n      if (forwardToSkan && this._skanManager) {\n        const skanProps = { ...(merged as Record<string, unknown>) };\n        if (skanProps.screen_name === undefined) skanProps.screen_name = screenName;\n        void this._skanManager.processEvent('screen_view', skanProps).catch(() => {});\n      }\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  async setUserProperties(properties: UserProperties): Promise<void> {\n    this.core.setUserProperties(properties as Record<string, unknown>);\n    this.sendUserPropertiesAsync(properties as Record<string, unknown>, false);\n  }\n\n  async setUserPropertiesOnce(properties: UserProperties): Promise<void> {\n    this.core.setUserPropertiesOnce(properties as Record<string, unknown>);\n    this.sendUserPropertiesAsync(properties as Record<string, unknown>, true);\n  }\n\n  async setConsent(consent: ConsentState): Promise<void> {\n    this.core.setConsent(consent);\n  }\n\n  // ── Tier 4: feature flags ──────────────────────────────────────────\n\n  /** Evaluate a feature flag. Emits `$feature_flag_called` (deduped per session). */\n  getFeatureFlag(flagKey: string): FeatureFlagValue | undefined {\n    return this.core.getFeatureFlag(flagKey);\n  }\n\n  /** Convenience: returns true iff `getFeatureFlag(flagKey)` is truthy. */\n  isFeatureEnabled(flagKey: string): boolean {\n    return this.core.isFeatureEnabled(flagKey);\n  }\n\n  /** Look up the JSON payload attached to a flag. Does NOT emit exposure events. */\n  getFeatureFlagPayload<T = unknown>(flagKey: string): T | undefined {\n    return this.core.getFeatureFlagPayload<T>(flagKey);\n  }\n\n  /** Snapshot every accessible flag's current value. */\n  getAllFlags(): Record<string, FeatureFlagValue> {\n    return this.core.getAllFlags();\n  }\n\n  /** Force a /config refresh and re-fire any registered listeners. */\n  async reloadFeatureFlags(): Promise<void> {\n    return this.core.reloadFeatureFlags();\n  }\n\n  /** Override person properties used for flag evaluation only. */\n  setPersonPropertiesForFlags(properties: Record<string, unknown>): void {\n    try {\n      this.core.setPersonPropertiesForFlags(properties);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Merge additional person properties into the flag-evaluation map. */\n  mergePersonPropertiesForFlags(properties: Record<string, unknown>): void {\n    try {\n      this.core.mergePersonPropertiesForFlags(properties);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Subscribe to feature-flag refreshes. Returns an unsubscribe function. */\n  onFeatureFlags(callback: FeatureFlagsListener): () => void {\n    return this.core.onFeatureFlags(callback);\n  }\n\n  /** Seed bootstrap flag values + payloads after init. */\n  setFeatureFlagBootstrap(bootstrap: FeatureFlagBootstrap): void {\n    this.core.setFeatureFlagBootstrap(bootstrap);\n  }\n\n  // ── Tier 1: super-properties ─────────────────────────────────────────\n\n  /**\n   * Register one or more super-properties — auto-merged into every track /\n   * screen call until cleared. Super-properties carry app-wide context\n   * (plan tier, region, experiment bucket).\n   */\n  setSuperProperties(properties: Record<string, unknown>): void {\n    try {\n      this.core.setSuperProperties(properties);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Register super-properties only if their keys have not been set before. */\n  setSuperPropertiesOnce(properties: Record<string, unknown>): void {\n    try {\n      this.core.setSuperPropertiesOnce(properties);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Remove a single super-property by key. */\n  unregisterSuperProperty(key: string): void {\n    try {\n      this.core.unregisterSuperProperty(key);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Clear all registered super-properties. */\n  clearSuperProperties(): void {\n    try {\n      this.core.clearSuperProperties();\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Snapshot the currently-registered super-properties. */\n  getSuperProperties(): Record<string, unknown> {\n    return this.core.getSuperProperties();\n  }\n\n  // ── Tier 1: timed events ─────────────────────────────────────────────\n\n  /** Start a duration timer for the next `track(name)` call. */\n  timeEvent(eventName: string): void {\n    try {\n      this.core.timeEvent(eventName);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Cancel a timed event without emitting it. Returns elapsed ms (0 if none). */\n  cancelTimedEvent(eventName: string): number {\n    try {\n      return this.core.cancelTimedEvent(eventName);\n    } catch (e) {\n      this.emitError(e);\n      return 0;\n    }\n  }\n\n  // ── Tier 1: multi-group ──────────────────────────────────────────────\n\n  /** Set membership for a single group_type. Empty `groupId` removes the type. */\n  setGroup(groupType: string, groupId: string): void {\n    try {\n      this.core.setGroup(groupType, groupId);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Add a group membership without overwriting other types (alias for setGroup). */\n  addGroup(groupType: string, groupId: string): void {\n    try {\n      this.core.addGroup(groupType, groupId);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Remove a group_type from the membership map. */\n  removeGroup(groupType: string): void {\n    try {\n      this.core.removeGroup(groupType);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Snapshot the current $groups membership map. */\n  getGroups(): GroupsState {\n    return this.core.getGroups();\n  }\n\n  // ── Tier 1: user-property mutators ───────────────────────────────────\n\n  /** Increment a numeric user property by `delta`. */\n  increment(key: string, delta: number = 1): void {\n    try {\n      this.core.increment(key, delta);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Append a single value to a list-typed user property. */\n  append(key: string, value: unknown): void {\n    try {\n      this.core.append(key, value);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Union (set-add) values into a list-typed user property. */\n  union(key: string, values: unknown[]): void {\n    try {\n      this.core.union(key, values);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /** Remove a user property by key. */\n  unset(key: string): void {\n    try {\n      this.core.unset(key);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  // ── Tier 1: identity accessors ───────────────────────────────────────\n\n  /** Returns the current anonymous ID, rotated on `reset()`. */\n  getAnonymousId(): string | null {\n    return this.core.getAnonymousId();\n  }\n\n  /** Returns the current device ID (stable per install, rotated on `reset()`). */\n  getDeviceId(): string | null {\n    return this.core.getDeviceId();\n  }\n\n  /** Returns the monotonically-increasing session number. */\n  getSessionNumber(): number {\n    return this.core.getSessionNumber();\n  }\n\n  /** Returns the SDK first-open RFC3339 timestamp, or null. */\n  getFirstOpenTime(): string | null {\n    return this.core.getFirstOpenTime();\n  }\n\n  // ── Tier 1: before_send hook ─────────────────────────────────────────\n\n  /**\n   * Register a synchronous filter callback called for every track / screen\n   * before the event reaches the queue. Return `null` to drop, or a modified\n   * `BeforeSendEvent` to forward.\n   *\n   * Pass `null` to clear the hook.\n   */\n  setBeforeSend(hook: BeforeSendHook | null): void {\n    this.core.setBeforeSend(hook);\n  }\n\n  /**\n   * Request App Tracking Transparency authorization (iOS only).\n   * After the user responds, this method automatically:\n   *  - Collects IDFA if authorized\n   *  - Updates device info with IDFA and ATT status\n   *\n   * ATT controls IDFA availability only. This method does not change Layers\n   * consent; use setConsent() explicitly when the app wants to do that.\n   *\n   * Returns the ATT status string.\n   */\n  async requestTrackingPermission(): Promise<ATTStatus> {\n    // ATT exists only on iOS. On Android there is no ATTrackingManager to ask,\n    // so this remains a no-op and leaves both device context and consent\n    // untouched.\n    // `ATTStatus` has no 'not_applicable' member; 'not_determined' is what\n    // att.ts already returns when the native module is absent.\n    if (this.currentPlatform() === 'android') {\n      if (this.enableDebug) {\n        console.log(\n          '[Layers] requestTrackingPermission() is a no-op on Android — ' +\n            'ATT is iOS-only; consent left untouched.'\n        );\n      }\n      return 'not_determined';\n    }\n\n    // Auto-detect expo-tracking-transparency and prefer it when available\n    let status: ATTStatus;\n    try {\n      const ExpoTT = await import('expo-tracking-transparency').catch(() => null);\n      if (ExpoTT) {\n        const { status: expoStatus } = await ExpoTT.requestTrackingPermissionsAsync();\n        const statusMap: Record<string, ATTStatus> = {\n          granted: 'authorized',\n          denied: 'denied',\n          restricted: 'restricted',\n          undetermined: 'not_determined'\n        };\n        status = statusMap[expoStatus] ?? 'not_determined';\n      } else {\n        status = await requestATT();\n      }\n    } catch {\n      status = await requestATT();\n    }\n\n    // Update device context with ATT status and IDFA (if authorized)\n    const currentContext = this.core.getDeviceContext();\n    const updates: DeviceContext = { ...currentContext, attStatus: status };\n    // A prior authorization may have populated IDFA. Clear it first so a\n    // later denied/restricted/not-determined result cannot retain it.\n    delete updates.idfa;\n    if (status === 'authorized') {\n      try {\n        const idfa = await getAdvertisingId();\n        if (idfa) updates.idfa = idfa;\n      } catch {\n        // IDFA collection is best-effort\n      }\n    }\n    this.core.setDeviceContext(updates);\n\n    if (this.enableDebug) {\n      console.log(`[Layers] ATT status: ${status}; Layers consent unchanged.`);\n    }\n\n    return status;\n  }\n\n  /**\n   * Set the app user ID. Uses set-user-once semantics:\n   * once set, subsequent calls are ignored until clearAppUserId() is called.\n   */\n  setAppUserId(appUserId: string): void {\n    if (this.enableDebug) {\n      console.log(`[Layers] setAppUserId(\"${appUserId}\")`);\n    }\n    if (this.userIdLocked && this.appUserId) {\n      if (this.enableDebug) {\n        console.warn('[Layers] appUserId already set. Call clearAppUserId() first to change it.');\n      }\n      return;\n    }\n    this.appUserId = appUserId;\n    this.userIdLocked = true;\n    this.core.identify(appUserId);\n  }\n\n  clearAppUserId(): void {\n    this.appUserId = undefined;\n    this.userIdLocked = false;\n  }\n\n  /**\n   * Reset the SDK state: clears the user ID, unlocks set-user-once, drops\n   * super-properties + multi-group memberships, rotates device / anonymous\n   * IDs, and starts a new session.\n   *\n   * After reset(), the SDK behaves as if no user has been identified. Events\n   * tracked after reset() will not carry the previous user's identity.\n   */\n  reset(): void {\n    if (this.enableDebug) {\n      console.log('[Layers] reset()');\n    }\n    // Clear JS-side user state\n    this.appUserId = undefined;\n    this.userIdLocked = false;\n\n    // Delegate to the Rust core's reset() which:\n    //  - drops user_id\n    //  - rotates device_id + anonymous_id\n    //  - clears super-properties + groups\n    //  - drops queued events\n    //  - rotates session\n    try {\n      this.core.reset();\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /**\n   * Associate all subsequent events with a group (company, team, organization).\n   * Pass `undefined` or empty string to clear the group association.\n   */\n  group(groupId: string | undefined, properties?: EventProperties): void {\n    if (this.enableDebug) {\n      console.log(\n        `[Layers] group(${groupId ? `\"${groupId}\"` : 'undefined'}, ${Object.keys(properties ?? {}).length} properties)`\n      );\n    }\n    try {\n      this.core.group(groupId ?? '', properties);\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  getAppUserId(): string | undefined {\n    return this.appUserId;\n  }\n\n  getSessionId(): string {\n    return this.core.getSessionId();\n  }\n\n  getConsentState(): ConsentState {\n    return this.core.getConsentState();\n  }\n\n  setDeviceInfo(deviceInfo: DeviceInfoUpdate): void {\n    // MERGE, never replace. The core's setDeviceContext replaces the whole\n    // context (matching the Rust core), but this public wrapper API takes\n    // PARTIAL updates — callers pass `{ idfa, attStatus }` after an ATT\n    // prompt. Passing that straight through wiped platform/os/app version/\n    // install_id, and every event from that moment on reported as a `web`\n    // install with unknown everything. The Expo ATT helper hit exactly this.\n    const next: DeviceContext = { ...this.core.getDeviceContext() };\n    for (const key of Object.keys(deviceInfo) as Array<keyof DeviceContext>) {\n      const value = deviceInfo[key];\n      if (value === undefined) {\n        delete next[key];\n      } else {\n        Object.assign(next, { [key]: value });\n      }\n    }\n    this.core.setDeviceContext(next);\n  }\n\n  async flush(): Promise<void> {\n    try {\n      await this.core.flushAsync();\n    } catch (e) {\n      this.emitError(e);\n      this.core.flush();\n    }\n  }\n\n  /**\n   * Flush all queued events synchronously (blocking).\n   *\n   * Drains all batches from the queue and sends them via HTTP, awaiting\n   * each batch before proceeding. Does not return until all batches have\n   * been sent or failed.\n   *\n   * This is useful for AppState background transitions where the app may\n   * be suspended shortly after the call returns.\n   */\n  async flushBlocking(): Promise<void> {\n    try {\n      // Keep flushing until the queue is empty\n      let depth = this.core.queueDepth();\n      while (depth > 0) {\n        await this.core.flushAsync();\n        const newDepth = this.core.queueDepth();\n        // Break if no progress was made (avoids infinite loop on persistent failures)\n        if (newDepth >= depth) break;\n        depth = newDepth;\n      }\n    } catch (e) {\n      this.emitError(e);\n    }\n  }\n\n  /**\n   * Register an error listener. Errors from track/screen/flush\n   * that would otherwise be silently dropped are forwarded here.\n   */\n  on(event: 'error', listener: ErrorListener): this {\n    if (event === 'error') this.errorListeners.add(listener);\n    return this;\n  }\n\n  /**\n   * Remove a previously registered error listener.\n   */\n  off(event: 'error', listener: ErrorListener): this {\n    if (event === 'error') this.errorListeners.delete(listener);\n    return this;\n  }\n\n  /**\n   * Set a listener to receive SDK initialization timing metrics.\n   * Must be called **before** `init()` to receive the callback.\n   * Pass `null` to clear the listener.\n   *\n   * @param listener A function receiving `(mainThreadDurationMs, totalDurationMs)`.\n   *   `mainThreadDurationMs` is the time spent before background work begins.\n   *   `totalDurationMs` is the total wall-clock time of the `init()` call.\n   */\n  setInitListener(listener: InitListener | null): void {\n    this._initListener = listener;\n  }\n\n  /**\n   * Returns the AdServices attribution token (iOS only), or null if not\n   * available. Collected automatically during init() on iOS 14.3+.\n   * Does NOT require ATT consent.\n   */\n  getAdServicesToken(): string | null {\n    return this._adServicesToken;\n  }\n\n  /**\n   * Returns the Google Play install referrer data (Android only), or null\n   * if not available. Collected automatically during init() on Android.\n   */\n  getInstallReferrer(): InstallReferrerData | null {\n    return this._installReferrer;\n  }\n\n  /**\n   * Returns the auto-configured SKANManager instance, or null if SKAN was not\n   * configured by the server's remote config.\n   * On iOS, when remote config contains a `skan` section with `preset` or\n   * `customRules`, the SDK automatically creates and configures a SKANManager.\n   * Every `track()` call is automatically forwarded to SKAN rule evaluation.\n   */\n  getSkanManager(): SKANManagerImpl | null {\n    return this._skanManager;\n  }\n\n  // --- Debug state accessors (used by LayersDebugOverlay) ---\n\n  /** Whether the SDK has completed async initialization. */\n  get isInitialized(): boolean {\n    return this._isInitialized;\n  }\n\n  /** The configured environment ('development' | 'staging' | 'production'). */\n  get environment(): Environment {\n    return this.config.environment;\n  }\n\n  /** The configured app ID. */\n  get appId(): string {\n    return this.config.appId;\n  }\n\n  /** Number of events currently queued. */\n  getQueueDepth(): number {\n    return this.core.queueDepth();\n  }\n\n  /**\n   * Return the per-device DebugView token, or null if `debug` was not enabled\n   * in the constructor config.\n   *\n   * When debug mode is on the SDK persists a stable UUID via AsyncStorage\n   * and sends it in the `X-Debug-Token` header on every event upload. This\n   * lets dashboard UIs filter the live tail to events from this device only.\n   */\n  getDebugToken(): string | null {\n    return this.core.getDebugToken();\n  }\n\n  /** Current device context from the core. */\n  getDeviceContext(): DeviceContext {\n    return this.core.getDeviceContext();\n  }\n\n  /** Whether the SDK believes the device is online. */\n  getNetworkStatus(): boolean {\n    return this.isOnline;\n  }\n\n  /** SDK version string. */\n  getSdkVersion(): string {\n    return SDK_VERSION;\n  }\n\n  /**\n   * Returns the most recent tracked events (newest first).\n   * Each entry is a formatted string like \"12:34:56 event_name (3 props)\".\n   */\n  getRecentEvents(): readonly string[] {\n    return this._recentEvents;\n  }\n\n  /**\n   * POST a device fingerprint to /clicks/resolve so the server can match this\n   * first-launch install to a recent /c/:appId click captured in the browser\n   * before the App Store / Play Store redirect. On a successful match, persist\n   * the returned click IDs via setAttributionData so every subsequent event\n   * (app_open, purchase_success, etc.) carries fbclid / gclid / ttclid / etc.\n   *\n   * This is the primary iOS web-to-app attribution path, since iOS has no\n   * equivalent of Android's Play Install Referrer. Also serves as a fallback\n   * for Android installs where the Install Referrer API returned nothing\n   * (sideload, Amazon Appstore, testing).\n   *\n   * Best-effort: all errors are swallowed — this must never block app_open.\n   *\n   * Two gates, both required. The caller checks the `fingerprint_resolve_enabled`\n   * remote-config switch; this method checks the core's delivery policy via\n   * `shouldAttemptSideRequest()`, so a device that denied analytics consent, is\n   * sending DNT, is inside a server Retry-After window, or is behind an open\n   * circuit breaker sends no fingerprint. The gate lives HERE, not only at the\n   * call site, so a future caller inherits it.\n   */\n  private async resolveClickFromFingerprint(): Promise<void> {\n    // The same policy an event batch answers to (ADR 0001) — consent, DNT,\n    // Retry-After, circuit breaker — read WITHOUT the flush gate's side\n    // effects. Nothing about a fingerprint probe deserves to outrank a consent\n    // denial, and nothing about it justifies spending the breaker's half-open\n    // probe or clearing a concurrent batch's `$first_open` delivery claim,\n    // which is what `shouldAttemptFlush()` + `abortFlushAttempt()` would do.\n    if (!this.core.shouldAttemptSideRequest()) {\n      if (this.enableDebug) {\n        console.log('[Layers] /clicks/resolve skipped — core delivery gate closed');\n      }\n      return;\n    }\n\n    const baseUrl = (this.config.baseUrl ?? 'https://in.layers.com').replace(/\\/$/, '');\n    const deviceContext = this.core.getDeviceContext() as Record<string, unknown>;\n\n    let platform: string | undefined;\n    try {\n      const { Platform } = require('react-native');\n      platform = Platform.OS as string;\n    } catch {\n      platform = undefined;\n    }\n\n    // Intentionally no client-supplied timestamp — the server anchors the\n    // /clicks/resolve lookback window to its own now() to prevent callers\n    // from scanning historical clicks with crafted past timestamps.\n    const fingerprint = {\n      app_id: this.config.appId,\n      platform,\n      device_model: deviceContext.deviceModel ?? null,\n      os_version: deviceContext.osVersion ?? null,\n      locale: deviceContext.locale ?? null,\n      timezone: deviceContext.timezone ?? null,\n      screen_size: deviceContext.screenSize ?? null\n    };\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), 3000);\n    let response: Response;\n    try {\n      response = await fetch(`${baseUrl}/clicks/resolve`, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          'X-App-Id': this.config.appId,\n          // From the core so this endpoint reports the same engine token as\n          // /events. On React Native that is always `engine/js` — Metro cannot\n          // bundle a `.wasm`, so the TypeScript engine is the only one that\n          // ever runs here — but reading it from the core keeps that a fact the\n          // SDK observes rather than a claim this line asserts.\n          'X-SDK-Version': this.core.sdkVersionHeader()\n        },\n        body: JSON.stringify(fingerprint),\n        signal: controller.signal\n      });\n    } finally {\n      clearTimeout(timeoutId);\n    }\n\n    if (!response.ok) {\n      if (this.enableDebug) {\n        console.log(`[Layers] /clicks/resolve status=${response.status}`);\n      }\n      return;\n    }\n\n    const body = (await response.json()) as {\n      success?: boolean;\n      data?: {\n        matched?: boolean;\n        fbclid?: string | null;\n        gclid?: string | null;\n        ttclid?: string | null;\n        msclkid?: string | null;\n        click_id?: string | null;\n        method?: string;\n        confidence?: number;\n      };\n    };\n\n    if (!body?.success || !body.data?.matched) {\n      if (this.enableDebug) {\n        console.log('[Layers] /clicks/resolve no-match');\n      }\n      return;\n    }\n\n    const { fbclid, gclid, ttclid, msclkid, method, confidence } = body.data;\n    if (!fbclid && !gclid && !ttclid && !msclkid) return;\n\n    try {\n      await this.setAttributionData(\n        this._attributionDeeplinkId,\n        gclid ?? this._attributionGclid,\n        fbclid ?? this._attributionFbclid,\n        ttclid ?? this._attributionTtclid,\n        msclkid ?? this._attributionMsclkid\n      );\n      if (this.enableDebug) {\n        console.log(\n          `[Layers] /clicks/resolve matched method=${String(method)} confidence=${String(confidence)} fbclid=${String(fbclid)}`\n        );\n      }\n    } catch {\n      // best-effort\n    }\n  }\n\n  /**\n   * Fire-and-forget POST to /users/properties.\n   * Best-effort: errors are silently swallowed.\n   */\n  private sendUserPropertiesAsync(properties: Record<string, unknown>, setOnce: boolean): void {\n    const baseUrl = (this.config.baseUrl ?? 'https://in.layers.com').replace(/\\/$/, '');\n    const appUserId = this.appUserId ?? this.core.getSessionId();\n    // The core's device_id — the same value already attached to every event.\n    // The server joins the (device_id, user_id) pair to stitch anonymous\n    // activity onto the identified profile, so omitting it here left every\n    // /users/properties upsert unstitchable. Read straight off the core (it is\n    // synchronous, like getSessionId above) so no cached copy can go stale\n    // when reset() rotates the id.\n    const deviceId = this.core.getDeviceId();\n    const payload: Record<string, unknown> = {\n      app_id: this.config.appId,\n      app_user_id: appUserId,\n      properties,\n      timestamp: new Date().toISOString()\n    };\n    if (deviceId) {\n      payload.device_id = deviceId;\n    }\n    if (setOnce) {\n      payload.set_once = true;\n    }\n\n    fetch(`${baseUrl}/users/properties`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'X-App-Id': this.config.appId,\n        // From the core, like /clicks/resolve above and /events itself. Reading\n        // it off `this.core` is safe here for the same reason `getSessionId`\n        // and `getDeviceId` are, five lines up: the core is constructed in the\n        // LayersReactNative constructor and this method is unreachable before\n        // that returns.\n        'X-SDK-Version': this.core.sdkVersionHeader()\n      },\n      body: JSON.stringify(payload)\n    }).catch(() => {\n      // Best-effort — don't throw on network errors\n    });\n  }\n\n  private recordRecentEvent(eventName: string, properties?: EventProperties): void {\n    const now = new Date();\n    const time = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;\n    const propCount = Object.keys(properties ?? {}).length;\n    const entry = `${time} ${eventName}${propCount > 0 ? ` (${String(propCount)} props)` : ''}`;\n    this._recentEvents.unshift(entry);\n    if (this._recentEvents.length > LayersReactNative.MAX_RECENT_EVENTS) {\n      this._recentEvents.pop();\n    }\n  }\n\n  private emitError(error: unknown): void {\n    const err = error instanceof Error ? error : new Error(String(error));\n    for (const listener of this.errorListeners) {\n      try {\n        listener(err);\n      } catch {}\n    }\n    if (this.enableDebug && this.errorListeners.size === 0) {\n      console.warn('[Layers]', err.message);\n    }\n  }\n\n  shutdown(): void {\n    // Invalidate any init() still in flight (see isRunAbandoned) and release\n    // the idempotency latch so a later init() can start a fresh run.\n    this._initGeneration += 1;\n    this._initPromise = null;\n    this._isInitialized = false;\n    // shutdown() owns everything installed up to this moment and tears it all\n    // down below. Handing the (now empty) runtime to the new generation means\n    // an older run reaching a checkpoint later can never claim it.\n    this._runtimeGeneration = this._initGeneration;\n\n    this.teardownRuntime();\n    this._attributionDeeplinkId = null;\n    this._attributionGclid = null;\n    this._attributionFbclid = null;\n    this._attributionFbc = null;\n    this._attributionTtclid = null;\n    this._attributionMsclkid = null;\n    this._initListener = null;\n    // Clear the SKAN facade + arming so a re-init re-arms a fresh window. The\n    // core owns the install-scoped floor (preserved across reset, cleared by the\n    // core on shutdown).\n    this._skanManager = null;\n    this._skanArmed = false;\n    setSkanCore(null);\n  }\n\n  /**\n   * Make a fatal error's `$exception` durable before React Native's crash\n   * path ends the process. The core's `flush()` snapshots the queue to the\n   * persistence backend, whose AsyncStorage write is otherwise fire-and-\n   * forget; this waits for that write to land. Delivery is left to the next\n   * launch, which re-hydrates the snapshot and sends it, so the crashed app is\n   * held open only for the write.\n   */\n  private async persistForCrash(): Promise<void> {\n    try {\n      this.core.flush();\n    } catch {\n      // The snapshot is best effort; the write wait still runs.\n    }\n    if (this.persistenceSettle) await this.persistenceSettle();\n  }\n\n  /**\n   * Remove every listener and timer this instance installed, then shut the\n   * core down. Shared by shutdown() and the mid-init abort path, so it must be\n   * safe to call twice — every handle is nulled and the core's own shutdown()\n   * is a no-op once it has run.\n   */\n  private teardownRuntime(): void {\n    this.stopConfigPolling();\n    // A pending probe belongs to the listener being torn down; letting it\n    // fire would warn about an instance nobody holds any more.\n    this.cancelLifecycleReachabilityCheck();\n    if (this.appStateSubscription) {\n      this.appStateSubscription.remove();\n      this.appStateSubscription = null;\n    }\n    if (this.netInfoUnsubscribe) {\n      this.netInfoUnsubscribe();\n      this.netInfoUnsubscribe = null;\n    }\n    if (this.deepLinkUnsubscribe) {\n      this.deepLinkUnsubscribe();\n      this.deepLinkUnsubscribe = null;\n    }\n    if (this.exceptionUninstall) {\n      // A host that swapped `ErrorUtils` since install can make the restore\n      // throw; the rest of the teardown must still run.\n      try {\n        this.exceptionUninstall();\n      } catch {\n        // Nothing left to restore.\n      }\n      this.exceptionUninstall = null;\n    }\n    try {\n      this.core.shutdown();\n    } catch {\n      // Already shut down (double teardown) — nothing to do.\n    }\n  }\n\n  // --- Attribution Data ---\n\n  /**\n   * Store attribution data that will be attached to all subsequent events.\n   *\n   * The values are persisted in AsyncStorage so they survive app restarts.\n   * Pass `null` to clear a value.\n   *\n   * When set, click IDs (`gclid`, `fbclid`, `ttclid`, `msclkid`) are included\n   * in every event's properties. For fbclid, a formatted `$fbc` parameter\n   * (fb.1.{timestamp}.{fbclid}) is also included.\n   *\n   * @param deeplinkId Deep link identifier for server-side attribution matching.\n   * @param gclid Google Click Identifier from ad click URLs.\n   * @param fbclid Facebook Click Identifier from ad click URLs.\n   * @param ttclid TikTok Click Identifier from ad click URLs.\n   * @param msclkid Microsoft Click Identifier from ad click URLs.\n   */\n  async setAttributionData(\n    deeplinkId: string | null = null,\n    gclid: string | null = null,\n    fbclid: string | null = null,\n    ttclid: string | null = null,\n    msclkid: string | null = null\n  ): Promise<void> {\n    this._attributionDeeplinkId = deeplinkId;\n    this._attributionGclid = gclid;\n    this._attributionFbclid = fbclid;\n    this._attributionFbc = fbclid != null ? formatFbc(fbclid) : null;\n    this._attributionTtclid = ttclid;\n    this._attributionMsclkid = msclkid;\n\n    // Update the Rust core's DeviceContext with the new deeplink_id so the\n    // top-level event field is populated (not just the properties bag).\n    try {\n      const currentContext = this.core.getDeviceContext();\n      const ctx: DeviceContext = { ...currentContext };\n      if (deeplinkId) {\n        ctx.deeplinkId = deeplinkId;\n      } else {\n        delete ctx.deeplinkId;\n      }\n      this.core.setDeviceContext(ctx);\n    } catch {\n      // DeviceContext update is best-effort\n    }\n\n    try {\n      const AsyncStorage = await loadAsyncStorage();\n      if (AsyncStorage) {\n        if (deeplinkId != null) {\n          await AsyncStorage.setItem(LayersReactNative.ATTRIBUTION_DEEPLINK_ID_KEY, deeplinkId);\n        } else {\n          await AsyncStorage.removeItem(LayersReactNative.ATTRIBUTION_DEEPLINK_ID_KEY);\n        }\n        if (gclid != null) {\n          await AsyncStorage.setItem(LayersReactNative.ATTRIBUTION_GCLID_KEY, gclid);\n        } else {\n          await AsyncStorage.removeItem(LayersReactNative.ATTRIBUTION_GCLID_KEY);\n        }\n        if (fbclid != null) {\n          await AsyncStorage.setItem(LayersReactNative.ATTRIBUTION_FBCLID_KEY, fbclid);\n        } else {\n          await AsyncStorage.removeItem(LayersReactNative.ATTRIBUTION_FBCLID_KEY);\n        }\n        if (this._attributionFbc != null) {\n          await AsyncStorage.setItem(LayersReactNative.ATTRIBUTION_FBC_KEY, this._attributionFbc);\n        } else {\n          await AsyncStorage.removeItem(LayersReactNative.ATTRIBUTION_FBC_KEY);\n        }\n        if (ttclid != null) {\n          await AsyncStorage.setItem(LayersReactNative.ATTRIBUTION_TTCLID_KEY, ttclid);\n        } else {\n          await AsyncStorage.removeItem(LayersReactNative.ATTRIBUTION_TTCLID_KEY);\n        }\n        if (msclkid != null) {\n          await AsyncStorage.setItem(LayersReactNative.ATTRIBUTION_MSCLKID_KEY, msclkid);\n        } else {\n          await AsyncStorage.removeItem(LayersReactNative.ATTRIBUTION_MSCLKID_KEY);\n        }\n      }\n    } catch {\n      // Persistence is best-effort\n    }\n\n    if (this.enableDebug) {\n      console.log(\n        `[Layers] setAttributionData(deeplinkId=${String(deeplinkId)}, gclid=${String(gclid)}, fbclid=${String(fbclid)}, ttclid=${String(ttclid)}, msclkid=${String(msclkid)})`\n      );\n    }\n  }\n\n  /**\n   * Merge attribution properties (click IDs) into the given event properties,\n   * if any attribution data is set. `deeplink_id` flows through DeviceContext\n   * on the Rust core (set via `setAttributionData`), not through properties.\n   */\n  private mergeAttributionProperties(properties?: EventProperties): EventProperties | undefined {\n    const merged: Record<string, unknown> = { ...(properties ?? {}) };\n    let hasAttribution = false;\n\n    if (this._attributionGclid != null && merged.gclid == null) {\n      merged.gclid = this._attributionGclid;\n      hasAttribution = true;\n    }\n    if (this._attributionFbclid != null && merged.fbclid == null) {\n      merged.fbclid = this._attributionFbclid;\n      hasAttribution = true;\n    }\n    if (this._attributionFbc != null && merged['$fbc'] == null) {\n      merged['$fbc'] = this._attributionFbc;\n      hasAttribution = true;\n    }\n    if (this._attributionTtclid != null && merged.ttclid == null) {\n      merged.ttclid = this._attributionTtclid;\n      hasAttribution = true;\n    }\n    if (this._attributionMsclkid != null && merged.msclkid == null) {\n      merged.msclkid = this._attributionMsclkid;\n      hasAttribution = true;\n    }\n\n    return hasAttribution ? (merged as EventProperties) : properties;\n  }\n\n  /**\n   * Restore persisted attribution data from AsyncStorage.\n   * Called during initialization to survive app restarts.\n   */\n  private async restoreAttributionData(): Promise<void> {\n    try {\n      const AsyncStorage = await loadAsyncStorage();\n      if (!AsyncStorage) return;\n\n      const [deeplinkId, gclid, fbclid, fbc, ttclid, msclkid] = await Promise.all([\n        AsyncStorage.getItem(LayersReactNative.ATTRIBUTION_DEEPLINK_ID_KEY),\n        AsyncStorage.getItem(LayersReactNative.ATTRIBUTION_GCLID_KEY),\n        AsyncStorage.getItem(LayersReactNative.ATTRIBUTION_FBCLID_KEY),\n        AsyncStorage.getItem(LayersReactNative.ATTRIBUTION_FBC_KEY),\n        AsyncStorage.getItem(LayersReactNative.ATTRIBUTION_TTCLID_KEY),\n        AsyncStorage.getItem(LayersReactNative.ATTRIBUTION_MSCLKID_KEY)\n      ]);\n\n      this._attributionDeeplinkId = deeplinkId;\n      this._attributionGclid = gclid;\n      this._attributionFbclid = fbclid;\n      this._attributionFbc = fbc;\n      this._attributionTtclid = ttclid;\n      this._attributionMsclkid = msclkid;\n\n      // Sync restored deeplink_id to the Rust core's DeviceContext so the\n      // top-level event field is populated from the first event onward.\n      if (deeplinkId != null) {\n        try {\n          const currentContext = this.core.getDeviceContext();\n          this.core.setDeviceContext({ ...currentContext, deeplinkId });\n        } catch {\n          // best-effort\n        }\n      }\n\n      if (\n        this.enableDebug &&\n        (deeplinkId != null || gclid != null || fbclid != null || ttclid != null || msclkid != null)\n      ) {\n        console.log(\n          `[Layers] Restored attribution data: deeplinkId=${String(deeplinkId)}, gclid=${String(gclid)}, fbclid=${String(fbclid)}, ttclid=${String(ttclid)}, msclkid=${String(msclkid)}`\n        );\n      }\n    } catch {\n      // Restoration is best-effort\n    }\n  }\n\n  // --- Platform-specific: Deep link auto-tracking ---\n\n  private _trackedDeepLinkUrls = new Set<string>();\n\n  private setupDeepLinkAutoTracking(): void {\n    if (this.deepLinkUnsubscribe) {\n      this.deepLinkUnsubscribe();\n      this.deepLinkUnsubscribe = null;\n    }\n    this.deepLinkUnsubscribe = setupDeepLinkListener((data: DeepLinkData) => {\n      // Deduplicate: getInitialURL and addEventListener can both fire for the\n      // same URL on some RN versions. Skip if we already tracked this exact URL.\n      if (this._trackedDeepLinkUrls.has(data.url)) return;\n      this._trackedDeepLinkUrls.add(data.url);\n      // Clear after a short delay so the same URL can be tracked if the user\n      // taps the same link again later\n      setTimeout(() => this._trackedDeepLinkUrls.delete(data.url), 2000);\n      try {\n        const properties: Record<string, unknown> = {\n          ...data.queryParams,\n          url: data.url,\n          scheme: data.scheme,\n          host: data.host,\n          path: data.path\n        };\n\n        // Extract well-known attribution parameters as top-level properties\n        // so they are easy to query server-side without parsing queryParams.\n        const attributionKeys = [\n          'fbclid',\n          'gclid',\n          'ttclid',\n          'msclkid',\n          'utm_source',\n          'utm_medium',\n          'utm_campaign',\n          'utm_term',\n          'utm_content',\n          'dclid',\n          'li_fat_id',\n          'twclid',\n          'scclid',\n          'irclickid',\n          'af_sub1',\n          'af_sub2',\n          'af_sub3',\n          'af_sub4',\n          'af_sub5'\n        ] as const;\n\n        for (const key of attributionKeys) {\n          if (data.queryParams[key]) {\n            properties[key] = data.queryParams[key];\n          }\n        }\n\n        // Auto-persist fbclid/gclid/ttclid/msclkid as attribution data\n        // so they flow into all subsequent events.\n        const fbclid = data.queryParams.fbclid;\n        const gclid = data.queryParams.gclid;\n        const ttclid = data.queryParams.ttclid;\n        const msclkid = data.queryParams.msclkid;\n        if (fbclid || gclid || ttclid || msclkid) {\n          void this.setAttributionData(\n            this._attributionDeeplinkId,\n            gclid || this._attributionGclid,\n            fbclid || this._attributionFbclid,\n            ttclid || this._attributionTtclid,\n            msclkid || this._attributionMsclkid\n          );\n        }\n\n        this.track('deep_link_opened', properties);\n        if (this.enableDebug) {\n          console.log(`[Layers] auto-tracked deep_link_opened: ${data.url}`);\n        }\n      } catch (e) {\n        this.emitError(e);\n      }\n    });\n  }\n\n  // --- Platform-specific: React Native device info ---\n\n  private async initializeDeviceInfo(): Promise<void> {\n    try {\n      const { Platform, Dimensions, NativeModules } = require('react-native');\n      const window = Dimensions.get('window');\n\n      const platform =\n        Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'react-native';\n      const osVersion =\n        typeof Platform.Version === 'string' ? Platform.Version : String(Platform.Version);\n\n      // Device model: use platform constants where available\n      let deviceModel: string;\n      if (Platform.OS === 'android') {\n        const brand = Platform.constants?.Brand ?? '';\n        const model = Platform.constants?.Model ?? '';\n        deviceModel = brand && model ? `${brand} ${model}` : model || brand || 'Android';\n      } else if (Platform.OS === 'ios') {\n        // Try to get the real iOS model identifier from native module\n        try {\n          const model = await NativeModules.LayersDeviceInfo?.getModelIdentifier();\n          deviceModel = model || 'iPhone';\n        } catch {\n          deviceModel = 'iPhone';\n        }\n      } else {\n        deviceModel = 'unknown';\n      }\n\n      // Auto-detect app version from native platform APIs\n      let appVersion: string | undefined;\n      try {\n        appVersion = await getAppVersion(Platform, NativeModules);\n      } catch {\n        // App version collection is best-effort\n      }\n\n      const context: DeviceContext = {\n        platform: platform as PlatformType,\n        osVersion,\n        deviceModel,\n        locale: getLocale(),\n        screenSize: `${Math.round(window.width)}x${Math.round(window.height)}`,\n        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n        ...(appVersion != null && { appVersion })\n      };\n\n      // Auto-collect IDFV on iOS (first-party identifier, no ATT consent required)\n      if (Platform.OS === 'ios') {\n        try {\n          const idfv = await getVendorId();\n          if (idfv) context.idfv = idfv;\n        } catch {\n          // IDFV collection is best-effort\n        }\n      }\n\n      // Auto-collect GAID on Android (Google Advertising ID)\n      if (Platform.OS === 'android') {\n        try {\n          const gaid = await getGoogleAdvertisingId();\n          if (gaid) context.idfa = gaid.id;\n          if (gaid && gaid.isLimitAdTrackingEnabled != null) {\n            context.attStatus = gaid.isLimitAdTrackingEnabled ? 'denied' : 'authorized';\n          }\n        } catch {\n          // GAID collection is best-effort\n        }\n      }\n\n      // Check for prior SDK state before getOrSetInstallId creates a new one\n      try {\n        const AsyncStorage = await loadAsyncStorage();\n        if (AsyncStorage) {\n          const existingId = await AsyncStorage.getItem(INSTALL_ID_KEY);\n          this._hadPriorSdkState = existingId != null;\n        }\n      } catch {\n        // best-effort\n      }\n\n      // Auto-populate installId from persistent storage\n      try {\n        const installId = await getOrSetInstallId();\n        if (installId) context.installId = installId;\n      } catch {\n        // installId collection is best-effort\n      }\n\n      this.core.setDeviceContext(context);\n    } catch {\n      // React Native APIs not available (e.g. in tests)\n      const context: DeviceContext = {\n        platform: 'react-native',\n        osVersion: 'unknown',\n        deviceModel: 'unknown',\n        locale: 'en-US'\n      };\n      // Check for prior SDK state in fallback path too\n      try {\n        const AsyncStorage = await loadAsyncStorage();\n        if (AsyncStorage) {\n          const existingId = await AsyncStorage.getItem(INSTALL_ID_KEY);\n          this._hadPriorSdkState = existingId != null;\n        }\n      } catch {\n        // best-effort\n      }\n      // Still try to populate installId in fallback path\n      try {\n        const installId = await getOrSetInstallId();\n        if (installId) context.installId = installId;\n      } catch {\n        // best-effort\n      }\n      this.core.setDeviceContext(context);\n    }\n\n    // Collect attribution signals independently of device context\n    // so a failure above doesn't prevent attribution collection.\n    // Reset per-init state first so a subsequent init() run (e.g. after\n    // reset() or a hot reload) doesn't re-use install-referrer params\n    // from a prior session.\n    this._installReferrerParams = null;\n    try {\n      const { Platform } = require('react-native');\n      if (Platform.OS === 'ios') {\n        try {\n          const token = await getAdServicesToken();\n          if (token) this._adServicesToken = token;\n        } catch {\n          // best-effort\n        }\n      }\n    } catch {\n      // Platform not available\n    }\n\n    // Play install referrer. Outside the `require('react-native')` above on\n    // purpose: `collectInstallReferrer()` already returns null off Android, so\n    // the outer platform check was redundant — and it meant a `react-native`\n    // resolution failure silently took Android attribution down with it.\n    try {\n      const referrer = await this.collectInstallReferrer();\n      if (referrer) {\n        this._installReferrer = referrer;\n\n        // Parse click IDs out of the Play Install Referrer string and\n        // persist them via setAttributionData so subsequent events carry\n        // them. This is the primary Android path for Meta/TikTok\n        // web-to-app attribution: /c/:appId appends fbclid/ttclid/etc to\n        // the referrer, and the server matcher resolves them to the\n        // originating click.\n        //\n        // Merge with any previously restored attribution values (e.g. the\n        // user may have manually called setAttributionData earlier) so we\n        // don't clobber a deeplinkId that's already in effect.\n        const parsed = parseInstallReferrerUrl(referrer.referrerUrl);\n        // Stash ALL parsed params so the app_open props spread below\n        // picks them up (click_id, gbraid, wbraid, rclid, li_fat_id,\n        // sclid, utm_*) — not just the 4 click IDs setAttributionData\n        // supports. Always assign (even if empty) so a subsequent init()\n        // run with a blank referrer doesn't re-use stale values from a\n        // prior session.\n        this._installReferrerParams = Object.keys(parsed).length > 0 ? parsed : null;\n        if (parsed.fbclid || parsed.gclid || parsed.ttclid || parsed.msclkid) {\n          try {\n            await this.setAttributionData(\n              this._attributionDeeplinkId,\n              parsed.gclid ?? this._attributionGclid,\n              parsed.fbclid ?? this._attributionFbclid,\n              parsed.ttclid ?? this._attributionTtclid,\n              parsed.msclkid ?? this._attributionMsclkid\n            );\n          } catch {\n            // best-effort\n          }\n        }\n      }\n    } catch {\n      // best-effort\n    }\n  }\n\n  /**\n   * Read the Google Play install referrer.\n   *\n   * A method rather than a direct call to `getInstallReferrer()` so tests can\n   * drive the Android branch: the SDK reaches React Native through a real CJS\n   * `require('react-native')`, which no module mock intercepts — that is why\n   * this path had no test coverage until now. Same seam\n   * `requireReactNative()` provides for the AppState listener.\n   */\n  private collectInstallReferrer(): Promise<InstallReferrerData | null> {\n    return getInstallReferrer();\n  }\n\n  /**\n   * Emit the canonical `install_referrer` event (Android only).\n   *\n   * Wire parity with Kotlin (`InstallReferrerTracker`) and Unity\n   * (`AndroidModule.GetInstallReferrer`): one dedicated event carrying the raw\n   * referrer under `referrer`, the ReferrerDetails timestamps/version/instant\n   * flag, and every parsed attribution param — pinned by\n   * `schema/fixtures/install-referrer-event.json`.\n   *\n   * Emitted at most once per install (see `installReferrerTrackedKey`), before\n   * `app_install` / `app_open`, so the attribution signal reaches the server\n   * ahead of the events it explains.\n   *\n   * The click-ID hand-off to `setAttributionData` that Kotlin performs after\n   * emitting already happens in `initializeDeviceInfo`, which must run before\n   * `restoreAttributionData`'s values are consumed by any event.\n   */\n  private async emitInstallReferrerEvent(): Promise<void> {\n    const referrer = this._installReferrer;\n    if (!referrer?.referrerUrl) return;\n\n    // Once-only guard. A storage failure means we cannot tell whether the\n    // event already went out; emitting is the safe side (the server dedups on\n    // event_id and the referrer is stable), so only a definite \"already sent\"\n    // suppresses.\n    let AsyncStorage: AsyncStorageStatic | null = null;\n    try {\n      AsyncStorage = await loadAsyncStorage();\n      if (AsyncStorage) {\n        const already = await AsyncStorage.getItem(installReferrerTrackedKey(this.config.appId));\n        if (already === 'true') return;\n      }\n    } catch {\n      // best-effort\n    }\n\n    this.track('install_referrer', buildInstallReferrerProperties(referrer));\n\n    // Persist only once the event is out, same ordering rule as the\n    // first-launch flag: a flag written ahead of the emit would permanently\n    // suppress an event that never went anywhere.\n    try {\n      if (AsyncStorage) {\n        await AsyncStorage.setItem(installReferrerTrackedKey(this.config.appId), 'true');\n      }\n    } catch {\n      // best-effort\n    }\n  }\n\n  // --- Platform-specific: AppState listener ---\n\n  /**\n   * The `react-native` module as this package resolves it.\n   *\n   * A method rather than 16 scattered `require` calls at the lifecycle sites,\n   * so the module the listener binds to and the module the reachability check\n   * inspects are provably the same one.\n   */\n  private requireReactNative(): ReactNativeLike {\n    return require('react-native') as ReactNativeLike;\n  }\n\n  private setupAppStateListener(): void {\n    // Never stack a second subscription on top of a live one — the old handle\n    // would be unreachable and every lifecycle event would fire twice.\n    if (this.appStateSubscription) {\n      this.appStateSubscription.remove();\n      this.appStateSubscription = null;\n    }\n    this.cancelLifecycleReachabilityCheck();\n    try {\n      // A host-supplied AppState is authoritative: the consumer has told us\n      // which copy is live, so there is nothing left to detect.\n      const injected = this.config.appState;\n      const rn = injected ? null : this.requireReactNative();\n      const AppState = injected ?? rn?.AppState;\n      if (!AppState) throw new Error('react-native AppState is unavailable');\n\n      // Track lifecycle state changes so duplicate transitions (e.g.\n      // active → inactive → background) don't fire duplicate events.\n      let lastEmittedState: string | null = null;\n      this.appStateSubscription = AppState.addEventListener('change', (nextAppState: string) => {\n        if (nextAppState === 'background' || nextAppState === 'inactive') {\n          // Emit $app_background on the first background-ish transition.\n          // We collapse `inactive` (transient on iOS) into the same bucket\n          // since both indicate the user is leaving the foreground.\n          if (this.config.autoTrackAppLifecycle !== false && lastEmittedState !== 'background') {\n            try {\n              this.track('$app_background');\n              lastEmittedState = 'background';\n            } catch {\n              // best-effort — never block the AppState callback\n            }\n          }\n          // Use synchronous flush to persist events to disk before OS suspends\n          // (async HTTP may not complete before suspension)\n          this.core.flush();\n        }\n        if (nextAppState === 'active') {\n          if (\n            this.config.autoTrackAppLifecycle !== false &&\n            lastEmittedState !== null && // skip the very first 'active' on init\n            lastEmittedState !== 'active'\n          ) {\n            try {\n              this.track('$app_foreground');\n              lastEmittedState = 'active';\n            } catch {\n              // best-effort\n            }\n          } else if (lastEmittedState === null) {\n            // First observed 'active' — record without emitting (init handles app_open).\n            lastEmittedState = 'active';\n          }\n          if (this.isOnline) {\n            void this.core.flushAsync().catch(() => {});\n          }\n        }\n      }) as { remove: () => void } | null;\n\n      // A returned subscription is NOT proof the listener works. See\n      // scheduleLifecycleReachabilityCheck.\n      if (rn) this.scheduleLifecycleReachabilityCheck(rn);\n    } catch (e) {\n      // NEVER swallow this. The background transition is what takes the\n      // crash-safety snapshot and flushes on the way out; an SDK whose\n      // lifecycle listener failed to register loses every event queued since\n      // the last flush tick on any launch that ends in a kill, and does it\n      // silently. A device smoke found exactly that state, and the empty\n      // `catch {}` that used to be here is why nothing upstream could see it.\n      this.emitError(e);\n      console.warn(\n        '[Layers] AppState listener failed to register — $app_background and the ' +\n          'background crash-safety snapshot are disabled for this session:',\n        e\n      );\n    }\n  }\n\n  /**\n   * Ask, one tick later, whether the listener just registered can ever fire.\n   *\n   * `AppState.addEventListener` hands back a subscription object no matter\n   * which copy of react-native you call it on, and a duplicate copy's\n   * `RCTDeviceEventEmitter` is never fed by the native side — so the\n   * subscription is real, well-formed, and permanently silent. That is the\n   * exact state a release build of examples/expo was in: registration logged\n   * success on every launch while `$app_background`, `$app_foreground` and\n   * the crash-safety snapshot had not run once. Nothing in the SDK could see\n   * it, and nothing in the app could either.\n   *\n   * The check is DEFERRED by a tick because it reads `AppRegistry`, which the\n   * host populates from its entry file. An SDK constructed at module scope\n   * ahead of `AppRegistry.registerComponent` would otherwise look like a\n   * duplicate to itself — a false alarm about a healthy integration, which is\n   * a worse failure than the one being fixed.\n   */\n  private scheduleLifecycleReachabilityCheck(rn: ReactNativeLike): void {\n    this.cancelLifecycleReachabilityCheck();\n    this._lifecycleCheckTimer = setTimeout(() => {\n      this._lifecycleCheckTimer = null;\n      // `null` is \"cannot tell\" and stays silent; only a definite `false`\n      // reports.\n      if (hostAppIsRegisteredOn(rn) !== false) return;\n      const error = new Error(DUPLICATE_REACT_NATIVE_MESSAGE);\n      this.emitError(error);\n      // Unconditional, unlike emitError's debug-gated warn: this is a broken\n      // integration that silently loses data, and the consumer needs to see\n      // it whether or not they wired up an error listener.\n      console.warn(DUPLICATE_REACT_NATIVE_MESSAGE);\n    }, 0);\n  }\n\n  private cancelLifecycleReachabilityCheck(): void {\n    if (this._lifecycleCheckTimer !== null) {\n      clearTimeout(this._lifecycleCheckTimer);\n      this._lifecycleCheckTimer = null;\n    }\n  }\n\n  // --- Platform-specific: SKAN auto-configuration from remote config ---\n\n  private async configureSkanFromRemoteConfig(): Promise<void> {\n    // SKAN is iOS-only; skip on other platforms.\n    try {\n      const { Platform } = require('react-native');\n      if (Platform.OS !== 'ios') return;\n    } catch {\n      // react-native not available (e.g. test env) — proceed with SKAN config\n    }\n\n    try {\n      // Delegate ALL config parsing (preset/rules/ecommerce alias/unknown-preset\n      // clearing/disable) to the Rust core, which already holds the fetched remote\n      // config. The core is the single source of truth — no JS-side parsing.\n      this.core.skanConfigureFromRemoteConfig();\n\n      if (!this.core.skanIsEnabled()) {\n        // SKAN disabled or removed from config. We don't un-arm: Apple's window is\n        // per-install and stays open once armed.\n        this._skanManager = null;\n        return;\n      }\n\n      // Facade over the core for event forwarding + manual introspection.\n      if (!this._skanManager) {\n        this._skanManager = new SKANManagerImpl(undefined, this.core);\n      }\n\n      // Arm Apple's postback window exactly once per launch. Latch the flag only\n      // once the native register actually succeeds, so a failed/unavailable arm is\n      // retried on the next config change. Re-arming would reset the OS value.\n      if (!this._skanArmed) {\n        const armed = await this._skanManager.initialize(true);\n        if (armed) this._skanArmed = true;\n      }\n\n      if (this.enableDebug) {\n        console.log(\n          `[Layers] SKAN auto-configured from remote config: preset=${String(this.core.skanCurrentPreset())}`\n        );\n      }\n    } catch {\n      // SKAN auto-configuration is best-effort\n    }\n  }\n\n  // --- Remote config polling ---\n\n  private startConfigPolling(): void {\n    this.stopConfigPolling();\n    this._configPollTimer = setInterval(() => {\n      void this.pollRemoteConfig();\n    }, LayersReactNative.CONFIG_POLL_INTERVAL_MS);\n  }\n\n  private stopConfigPolling(): void {\n    if (this._configPollTimer) {\n      clearInterval(this._configPollTimer);\n      this._configPollTimer = null;\n    }\n  }\n\n  private async pollRemoteConfig(): Promise<void> {\n    try {\n      await this.core.fetchRemoteConfig();\n      // Re-run SKAN configuration to pick up any changes\n      await this.configureSkanFromRemoteConfig();\n      if (this.enableDebug) {\n        console.log('[Layers] Remote config updated (poll)');\n      }\n    } catch {\n      // Config polling is best-effort — errors are silently ignored\n    }\n  }\n\n  // --- Platform-specific: NetInfo listener ---\n\n  private setupNetInfoListener(): void {\n    if (this.netInfoUnsubscribe) {\n      this.netInfoUnsubscribe();\n      this.netInfoUnsubscribe = null;\n    }\n    try {\n      const NetInfo = require('@react-native-community/netinfo');\n      this.netInfoUnsubscribe = NetInfo.addEventListener(\n        (state: { isConnected: boolean | null }) => {\n          const wasOffline = !this.isOnline;\n          this.isOnline = state.isConnected !== false;\n\n          if (wasOffline && this.isOnline) {\n            void this.core.flushAsync().catch(() => {});\n          }\n        }\n      );\n    } catch {\n      // NetInfo not available — assume always online\n    }\n  }\n}\n\n// --- AsyncStorage persistence backend ---\n\ninterface AsyncStoragePersistence {\n  backend: PersistenceBackend;\n  /** Resolves once every AsyncStorage write started so far has finished. */\n  settle: () => Promise<void>;\n}\n\nasync function createAsyncStoragePersistence(appId: string): Promise<AsyncStoragePersistence> {\n  const prefix = `layers_sdk_${appId}_`;\n  const memoryStore = new Map<string, Uint8Array>();\n  // The backend's write() stays synchronous, so the AsyncStorage half of\n  // every write runs in the background. The set records what is still in\n  // flight so the fatal `$exception` path can wait for it before the\n  // process is allowed to die. writeToAsyncStorage never rejects; the\n  // rejection branch below only keeps the set honest.\n  const inFlight = new Set<Promise<void>>();\n  const trackInFlight = (operation: Promise<void>): void => {\n    const entry: Promise<void> = operation.then(\n      () => {\n        inFlight.delete(entry);\n      },\n      () => {\n        inFlight.delete(entry);\n      }\n    );\n    inFlight.add(entry);\n  };\n\n  // Pre-load from AsyncStorage so that events persisted in a prior session\n  // are available synchronously when the core hydrates on init.\n  try {\n    const AsyncStorage = await loadAsyncStorage();\n    if (AsyncStorage) {\n      const allKeys = await AsyncStorage.getAllKeys();\n      const sdkKeys = allKeys.filter((k) => k.startsWith(prefix));\n      if (sdkKeys.length > 0) {\n        const pairs = await readManyFromAsyncStorage(AsyncStorage, sdkKeys);\n        for (const [key, value] of pairs) {\n          if (value) {\n            const shortKey = key.slice(prefix.length);\n            memoryStore.set(shortKey, base64Decode(value));\n          }\n        }\n      }\n    }\n  } catch (error) {\n    // This pre-load IS the read half of persistence: everything the install\n    // knows about itself — `identity_state`, first-open bookkeeping, the\n    // queued events a previous launch never delivered — comes back through\n    // here. Writes go out on a separate path and keep succeeding when this\n    // fails, so a silent catch leaves the SDK writing durable state it can\n    // never read, and every launch reports itself as a brand-new install.\n    // That is exactly how AsyncStorage v3 shipped undetected.\n    warnPersistencePreloadFailed(error);\n  }\n\n  const backend: PersistenceBackend = {\n    write(key: string, data: Uint8Array): void {\n      memoryStore.set(key, new Uint8Array(data));\n      // Background write to AsyncStorage, tracked so settle() can wait on it.\n      trackInFlight(writeToAsyncStorage(prefix + key, data));\n    },\n    read(key: string): Uint8Array | null {\n      return memoryStore.get(key) ?? null;\n    },\n    delete(key: string): void {\n      memoryStore.delete(key);\n      trackInFlight(deleteFromAsyncStorage(prefix + key));\n    },\n    listKeys(keyPrefix: string): string[] {\n      const keys: string[] = [];\n      for (const k of memoryStore.keys()) {\n        if (k.startsWith(keyPrefix)) {\n          keys.push(k);\n        }\n      }\n      keys.sort();\n      return keys;\n    }\n  };\n\n  return {\n    backend,\n    settle: () => Promise.all(Array.from(inFlight)).then(() => undefined)\n  };\n}\n\n// Binary-safe base64 encoding that handles large arrays without stack overflow.\n// btoa(String.fromCharCode(...data)) spreads the entire array onto the call stack,\n// which crashes for event batches larger than ~125 KB.\nconst B64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst B64_LOOKUP = new Uint8Array(128);\nfor (let _i = 0; _i < B64_CHARS.length; _i += 1) {\n  B64_LOOKUP[B64_CHARS.charCodeAt(_i)] = _i;\n}\n\nfunction base64Decode(str: string): Uint8Array {\n  const lookup = B64_LOOKUP;\n  // Strip padding to determine decoded length\n  let len = str.length;\n  if (str[len - 1] === '=') len -= 1;\n  if (str[len - 1] === '=') len -= 1;\n  const bytes = new Uint8Array(Math.floor((len * 3) / 4));\n  let p = 0;\n  for (let i = 0; i < len; i += 4) {\n    const b0 = lookup[str.charCodeAt(i)]!;\n    const b1 = lookup[str.charCodeAt(i + 1)]!;\n    const b2 = i + 2 < len ? lookup[str.charCodeAt(i + 2)]! : 0;\n    const b3 = i + 3 < len ? lookup[str.charCodeAt(i + 3)]! : 0;\n    bytes[p] = (b0 << 2) | (b1 >> 4);\n    p += 1;\n    if (i + 2 < len) {\n      bytes[p] = ((b1 & 0x0f) << 4) | (b2 >> 2);\n      p += 1;\n    }\n    if (i + 3 < len) {\n      bytes[p] = ((b2 & 0x03) << 6) | b3;\n      p += 1;\n    }\n  }\n  return bytes;\n}\n\nfunction base64Encode(bytes: Uint8Array): string {\n  let result = '';\n  const len = bytes.length;\n  for (let i = 0; i < len; i += 3) {\n    const b0 = bytes[i]!;\n    const b1 = i + 1 < len ? bytes[i + 1]! : 0;\n    const b2 = i + 2 < len ? bytes[i + 2]! : 0;\n    result += B64_CHARS[(b0 >> 2)!]!;\n    result += B64_CHARS[((b0 & 0x03) << 4) | (b1 >> 4)]!;\n    result += i + 1 < len ? B64_CHARS[((b1 & 0x0f) << 2) | (b2 >> 6)]! : '=';\n    result += i + 2 < len ? B64_CHARS[b2 & 0x3f]! : '=';\n  }\n  return result;\n}\n\nasync function writeToAsyncStorage(key: string, data: Uint8Array): Promise<void> {\n  try {\n    const AsyncStorage = await loadAsyncStorage();\n    if (!AsyncStorage) return;\n    const encoded = base64Encode(data);\n    await AsyncStorage.setItem(key, encoded);\n  } catch {\n    // Best effort\n  }\n}\n\nasync function deleteFromAsyncStorage(key: string): Promise<void> {\n  try {\n    const AsyncStorage = await loadAsyncStorage();\n    if (!AsyncStorage) return;\n    await AsyncStorage.removeItem(key);\n  } catch {\n    // Best effort\n  }\n}\n\n/**\n * The slice of `@react-native-async-storage/async-storage` this SDK uses,\n * spanning every major version the peer range admits.\n *\n * `getItem`, `setItem`, `removeItem` and `getAllKeys` are identical in v1, v2\n * and v3. The batch read is the one thing that is not, so both spellings are\n * optional and `readManyFromAsyncStorage` picks whichever the installed copy\n * actually has.\n */\ninterface AsyncStorageStatic {\n  getItem(key: string): Promise<string | null>;\n  setItem(key: string, value: string): Promise<void>;\n  removeItem(key: string): Promise<void>;\n  getAllKeys(): Promise<readonly string[]>;\n  /** v1/v2 batch read, ordered pairs. Removed in v3. */\n  multiGet?(keys: readonly string[]): Promise<readonly (readonly [string, string | null])[]>;\n  /** v3 batch read, keyed lookup table. Absent in v1/v2. */\n  getMany?(keys: string[]): Promise<Record<string, string | null>>;\n}\n\nlet _warnedAsyncStorageBatchShape = false;\nlet _warnedPersistencePreloadFailed = false;\n\n/**\n * Batch-read `keys` through whichever multi-key API the installed AsyncStorage\n * exposes.\n *\n * v3.0.0 removed `multiGet` in favour of `getMany`, and changed the return\n * type with it: `multiGet` answers `[key, value][]`, `getMany` answers\n * `Record<key, value | null>`. Calling the v1/v2 name unconditionally threw\n * `TypeError: AsyncStorage.multiGet is not a function` on v3 — and because\n * `>=1.21.0` admits v3, and npm's `latest` has been v3 since v3.0.0, that was\n * the default install.\n */\nasync function readManyFromAsyncStorage(\n  storage: AsyncStorageStatic,\n  keys: readonly string[]\n): Promise<[string, string | null][]> {\n  if (typeof storage.multiGet === 'function') {\n    const pairs = await storage.multiGet(keys);\n    return pairs.map(([key, value]): [string, string | null] => [key, value ?? null]);\n  }\n\n  if (typeof storage.getMany === 'function') {\n    const record = await storage.getMany([...keys]);\n    // Indexed by the key that was asked for, rather than by iterating the\n    // result: the v3 contract is a lookup table, so a key that was not stored\n    // is free to be absent rather than present-and-null.\n    return keys.map((key): [string, string | null] => [key, record?.[key] ?? null]);\n  }\n\n  warnAsyncStorageBatchShape();\n  // Neither spelling. `getItem` has existed in every published version, so one\n  // read per key still restores the install's identity and its queued events —\n  // slower than a batch read, and never silently empty.\n  return Promise.all(\n    keys.map(async (key): Promise<[string, string | null]> => [key, await storage.getItem(key)])\n  );\n}\n\nfunction warnAsyncStorageBatchShape(): void {\n  if (_warnedAsyncStorageBatchShape) return;\n  _warnedAsyncStorageBatchShape = true;\n  // Not debug-gated. An unrecognised store is one behaviour change away from\n  // costing the install its identity, and the consumer is the only one who can\n  // pin the dependency.\n  console.warn(\n    '[Layers] @react-native-async-storage/async-storage exposes neither multiGet ' +\n      '(v1/v2) nor getMany (v3) — falling back to one read per key to restore ' +\n      'persisted identity and queued events. If launches start reporting as new ' +\n      'installs, pin async-storage to a version this SDK recognises (>=1.21.0).'\n  );\n}\n\nfunction warnPersistencePreloadFailed(error: unknown): void {\n  if (_warnedPersistencePreloadFailed) return;\n  _warnedPersistencePreloadFailed = true;\n  console.warn(\n    '[Layers] could not read persisted SDK state back from AsyncStorage — this ' +\n      'launch starts with a fresh identity and an empty event queue, and will ' +\n      'report itself as a new install. Check @react-native-async-storage/async-storage ' +\n      'compatibility; the SDK supports v1.21+, v2 and v3.',\n    error\n  );\n}\n\nlet _asyncStorage: AsyncStorageStatic | undefined = undefined;\nlet _asyncStoragePromise: Promise<AsyncStorageStatic | null> | null = null;\n\nasync function loadAsyncStorage(): Promise<AsyncStorageStatic | null> {\n  if (_asyncStorage) return _asyncStorage;\n  if (!_asyncStoragePromise) {\n    _asyncStoragePromise = import('@react-native-async-storage/async-storage')\n      .then((mod): AsyncStorageStatic | null => {\n        const storage = (mod.default ?? mod) as AsyncStorageStatic | null;\n        if (storage) _asyncStorage = storage;\n        return storage;\n      })\n      .catch((): null => null);\n  }\n  return _asyncStoragePromise;\n}\n\n// --- Locale helper ---\n\nfunction getLocale(): string {\n  try {\n    const { Platform, NativeModules } = require('react-native');\n    if (Platform.OS === 'ios') {\n      return (\n        NativeModules.SettingsManager?.settings?.AppleLocale ??\n        NativeModules.SettingsManager?.settings?.AppleLanguages?.[0] ??\n        'en-US'\n      );\n    }\n    if (Platform.OS === 'android') {\n      return NativeModules.I18nManager?.localeIdentifier ?? 'en-US';\n    }\n  } catch {\n    // Fallback\n  }\n  return 'en-US';\n}\n\n// --- Install ID via AsyncStorage ---\n\nconst INSTALL_ID_KEY = 'layers_install_id';\n\n/**\n * Pre-scoping first-launch key. Read-only from here on: it is global, so a\n * staging→production `appId` switch made every app share one flag. We still\n * consult it so installs that predate the scoping don't re-fire `app_install`\n * on upgrade, but nothing writes it any more.\n */\nconst FIRST_LAUNCH_TRACKED_KEY_LEGACY = '@layers/first_launch_tracked';\n\n/**\n * First-launch flag, scoped by `appId` to match the engine's app-scoped\n * identity state (`layers_sdk_<appId>_identity_state`). With a single global\n * key the two disagreed: switching `appId` made the engine re-emit\n * `$first_open` (fresh identity state) while the wrapper suppressed\n * `app_install` (flag already set) — a first-open with no install.\n */\nfunction firstLaunchTrackedKey(appId: string): string {\n  return `@layers/first_launch_tracked_${appId}`;\n}\n\n/**\n * Once-only guard for the `install_referrer` event, scoped by `appId` like the\n * first-launch flag above.\n *\n * Kotlin and Unity persist the equivalent flag in SharedPreferences\n * (`layers_referrer_collected`) and skip the whole fetch once set, because the\n * Play referrer describes the install and is the same string on every launch.\n * The React Native native module has no such guard — it re-fetches every init —\n * so the guard lives here, on the emit. Without it the SDK would post an\n * `install_referrer` event on every single app launch for the life of the\n * install.\n */\nfunction installReferrerTrackedKey(appId: string): string {\n  return `@layers/install_referrer_tracked_${appId}`;\n}\n/**\n * Last-known app_version persisted across launches. Used to detect\n * `$app_update` (version increment) vs. `$first_open` (no prior value).\n */\nconst LAST_APP_VERSION_KEY = '@layers/last_app_version';\n\n/**\n * Maximum age of an app installation (in milliseconds) for which the SDK\n * will consider the first launch as a genuine new install. If the app was\n * installed more than 24 hours ago AND no prior Layers SDK state exists,\n * the SDK treats this as an existing app that just added the SDK — not a\n * new install — and suppresses `is_first_launch = true`.\n */\nconst INSTALL_EVENT_MAX_DIFF_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Process-lifetime install ID used when AsyncStorage is unavailable. Minting a\n * fresh UUID per call made every launch — and every call within a launch —\n * look like a different install: rotating `install_id`, `app_install` +\n * `$first_open` on every start, no attribution persistence. Memoizing at least\n * keeps a single session internally consistent.\n */\nlet _ephemeralInstallId: string | null = null;\nlet _warnedNoAsyncStorage = false;\n\nfunction ephemeralInstallId(): string {\n  if (!_warnedNoAsyncStorage) {\n    _warnedNoAsyncStorage = true;\n    // Deliberately NOT debug-gated: this silently destroys install identity\n    // and attribution, and `@react-native-async-storage/async-storage` is an\n    // optional peer, so shipping without it is a realistic install shape.\n    console.warn(\n      '[Layers] @react-native-async-storage/async-storage is not installed — ' +\n        'install ID, first-launch state and the offline event queue cannot persist. ' +\n        'Every app launch will look like a new install. ' +\n        'Install the package to enable persistent identity.'\n    );\n  }\n  _ephemeralInstallId ??= generateUUID();\n  return _ephemeralInstallId;\n}\n\nexport async function getOrSetInstallId(): Promise<string> {\n  try {\n    const AsyncStorage = await loadAsyncStorage();\n    if (!AsyncStorage) return ephemeralInstallId();\n\n    const existing = await AsyncStorage.getItem(INSTALL_ID_KEY);\n    if (existing) return existing;\n\n    const newId = generateUUID();\n    await AsyncStorage.setItem(INSTALL_ID_KEY, newId);\n    return newId;\n  } catch {\n    return ephemeralInstallId();\n  }\n}\n\nfunction generateUUID(): string {\n  if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n    return crypto.randomUUID();\n  }\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n    const r = (Math.random() * 16) | 0;\n    const v = c === 'x' ? r : (r & 0x3) | 0x8;\n    return v.toString(16);\n  });\n}\n\n// --- Install Event Gating (24-hour window) ---\n\n/**\n * Read the app's first install time from the native module.\n * Returns milliseconds since epoch, or null if not available.\n *\n * - Android: reads PackageInfo.firstInstallTime via PackageManager\n * - iOS: reads the Documents directory creation date as a proxy\n */\nexport async function getFirstInstallTime(): Promise<number | null> {\n  try {\n    const { NativeModules } = require('react-native');\n    const mod = NativeModules.LayersDeviceInfo as\n      | { getFirstInstallTime?(): Promise<number | null> }\n      | undefined;\n    if (!mod?.getFirstInstallTime) return null;\n    const time = await mod.getFirstInstallTime();\n    return time != null && Number.isFinite(time) ? time : null;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Determine whether this is a genuine new install or an existing app that\n * just added the Layers SDK.\n *\n * This is a pure function that takes pre-resolved inputs — the caller is\n * responsible for reading and persisting the `FIRST_LAUNCH_TRACKED_KEY`\n * flag in AsyncStorage. This mirrors the Android/Swift implementations\n * where the caller manages persistence.\n *\n * Logic (mirrors Android's `shouldTreatAsNewInstall`):\n * 1. If `isFirstLaunchByFlag` is `false` (flag already tracked), return `false`.\n * 2. If the SDK had prior state (`install_id` already existed in\n *    AsyncStorage), trust the flag — this is a returning user whose\n *    first-launch flag was not yet written (e.g. upgrade from an older\n *    SDK version). Return `true`.\n * 3. If the SDK had NO prior state AND the app was installed more than\n *    24 hours ago, this is an existing app getting the SDK for the first\n *    time — suppress `is_first_launch`. Return `false`.\n * 4. If the SDK had no prior state AND the app was installed within 24\n *    hours, this is a genuine new install — allow `is_first_launch`.\n *    Return `true`.\n *\n * @param isFirstLaunchByFlag  Whether the first_launch flag has NOT been set yet (true = not yet tracked).\n * @param hadPriorSdkState     Whether `install_id` already existed before init.\n * @param enableDebug          Whether to log debug info.\n */\nexport async function shouldTreatAsNewInstall(\n  isFirstLaunchByFlag: boolean,\n  hadPriorSdkState: boolean,\n  enableDebug = false,\n  _getInstallTime: () => Promise<number | null> = getFirstInstallTime\n): Promise<boolean> {\n  // 1. Flag already tracked — not a first launch\n  if (!isFirstLaunchByFlag) return false;\n\n  // 2. SDK had prior state — trust the flag: this is a real first launch\n  if (hadPriorSdkState) return true;\n\n  // 3 & 4. SDK has no prior state — check whether the app is a recent install\n  try {\n    const firstInstallTime = await _getInstallTime();\n    if (firstInstallTime != null) {\n      const elapsed = Date.now() - firstInstallTime;\n      const isRecentInstall = elapsed <= INSTALL_EVENT_MAX_DIFF_MS;\n\n      if (!isRecentInstall && enableDebug) {\n        console.log(\n          `[Layers] Install event gated: app installed ${Math.round(elapsed / 1000)}s ago ` +\n            `(threshold=${INSTALL_EVENT_MAX_DIFF_MS / 1000}s), ` +\n            'no prior SDK state — suppressing is_first_launch'\n        );\n      }\n\n      return isRecentInstall;\n    }\n  } catch {\n    // best-effort\n  }\n\n  // If we can't read the install time, default to trusting it as a new install\n  return true;\n}\n\n// --- Clipboard Attribution (iOS-only deferred deep links) ---\n\nexport interface ClipboardAttribution {\n  clickUrl: string;\n  clickId: string;\n}\n\nconst CLIPBOARD_CHECKED_KEY = '@layers/clipboard_checked';\n\nexport async function readClipboardAttribution(): Promise<ClipboardAttribution | null> {\n  try {\n    const { Platform } = require('react-native');\n    if (Platform.OS !== 'ios') return null;\n  } catch {\n    return null;\n  }\n\n  // Check if we've already read clipboard (once per install)\n  try {\n    const AS = await loadAsyncStorage();\n    if (AS) {\n      const flag = await AS.getItem(CLIPBOARD_CHECKED_KEY);\n      if (flag === 'true') return null;\n    }\n  } catch {}\n\n  let text: string | null = null;\n  try {\n    // Try @react-native-clipboard/clipboard first (recommended)\n    const Clipboard = await import('@react-native-clipboard/clipboard')\n      .then((m) => m.default)\n      .catch(() => null);\n    if (Clipboard) {\n      text = await Clipboard.getString();\n    } else {\n      // Fallback to deprecated RN Clipboard\n      const { Clipboard: RNClipboard } = require('react-native');\n      if (RNClipboard) {\n        text = await RNClipboard.getString();\n      }\n    }\n  } catch {}\n\n  // Mark as checked regardless of result\n  try {\n    const AS = await loadAsyncStorage();\n    if (AS) await AS.setItem(CLIPBOARD_CHECKED_KEY, 'true');\n  } catch {}\n\n  if (!text) return null;\n\n  // Check for Layers click URL pattern\n  const match = text.match(/https?:\\/\\/(in\\.layers\\.com|link\\.layers\\.com)\\/c\\/([^?\\s]+)/);\n  if (!match) return null;\n\n  return { clickUrl: text, clickId: match[2]! };\n}\n\n// --- App Version Helper ---\n\n/**\n * Attempt to read the host app version from platform-specific APIs.\n * On Android, reads from PackageInfo via Platform.constants or a native module.\n * On iOS, reads from the LayersDeviceInfo native module which can access the Info.plist.\n */\nasync function getAppVersion(\n  Platform: { OS: string; constants?: Record<string, unknown> },\n  NativeModules: Record<string, unknown>\n): Promise<string | undefined> {\n  try {\n    // Android: Platform.constants may include the app version via ReactNativeVersion\n    // or we can use a native module\n    if (Platform.OS === 'android') {\n      const mod = NativeModules.LayersDeviceInfo as\n        | { getAppVersion?(): Promise<string | null> }\n        | undefined;\n      if (mod?.getAppVersion) {\n        const version = await mod.getAppVersion();\n        if (version) return version;\n      }\n    }\n\n    // iOS: use LayersDeviceInfo native module\n    if (Platform.OS === 'ios') {\n      const mod = NativeModules.LayersDeviceInfo as\n        | { getAppVersion?(): Promise<string | null> }\n        | undefined;\n      if (mod?.getAppVersion) {\n        const version = await mod.getAppVersion();\n        if (version) return version;\n      }\n    }\n  } catch {\n    // Best-effort\n  }\n  return undefined;\n}\n\n// --- Google Advertising ID (Android only) ---\n\nexport interface GoogleAdvertisingInfo {\n  id: string;\n  isLimitAdTrackingEnabled: boolean;\n}\n\n/**\n * Fetch the Google Advertising ID (GAID) on Android via NativeModules.\n * Returns null on iOS or when the native module is not available.\n */\nexport async function getGoogleAdvertisingId(): Promise<GoogleAdvertisingInfo | null> {\n  try {\n    const { NativeModules, Platform } = require('react-native');\n    if (Platform.OS !== 'android') return null;\n    const mod = NativeModules.LayersAdvertisingId as\n      | { getAdvertisingInfo?(): Promise<GoogleAdvertisingInfo | null> }\n      | undefined;\n    if (!mod?.getAdvertisingInfo) return null;\n    const info = await mod.getAdvertisingInfo();\n    if (!info || !info.id || info.id === '00000000-0000-0000-0000-000000000000') return null;\n    return info;\n  } catch {\n    return null;\n  }\n}\n\n// --- AdServices Attribution Token (iOS only) ---\n\n/**\n * Request the AdServices attribution token on iOS 14.3+.\n * Uses the LayersAdServices native module which calls AAAttribution.attributionToken().\n * Does NOT require ATT consent.\n * Returns null on Android or when the native module is not available.\n */\nexport async function getAdServicesToken(): Promise<string | null> {\n  try {\n    const { NativeModules, Platform } = require('react-native');\n    if (Platform.OS !== 'ios') return null;\n    const mod = NativeModules.LayersAdServices as\n      | { getAttributionToken?(): Promise<string | null> }\n      | undefined;\n    if (!mod?.getAttributionToken) return null;\n    return await mod.getAttributionToken();\n  } catch {\n    return null;\n  }\n}\n\n// --- Install Referrer (Android only) ---\n\nexport interface InstallReferrerData {\n  referrerUrl: string;\n  referrerClickTimestamp?: number;\n  installBeginTimestamp?: number;\n  /** ReferrerDetails.getReferrerClickTimestampServerSeconds() */\n  referrerClickTimestampServer?: number;\n  /** ReferrerDetails.getInstallBeginTimestampServerSeconds() */\n  installBeginTimestampServer?: number;\n  /** ReferrerDetails.getInstallVersion() */\n  installVersion?: string;\n  /** ReferrerDetails.getGooglePlayInstantParam() */\n  googlePlayInstant?: boolean;\n}\n\n/**\n * Known attribution params extracted from the Play Install Referrer string.\n * Keep in sync with layers sdk-ingest routes/click.ts::buildAndroidReferrerUrl\n * and the parallel allowlists in kotlin / unity / flutter SDKs.\n */\nconst INSTALL_REFERRER_ATTRIBUTION_PARAMS = new Set<string>([\n  // UTM\n  'utm_source',\n  'utm_medium',\n  'utm_campaign',\n  'utm_content',\n  'utm_term',\n  // Ad platform click IDs\n  'gclid',\n  'gbraid',\n  'wbraid', // Google\n  'fbclid', // Meta\n  'ttclid', // TikTok\n  'twclid', // X\n  'msclkid', // Microsoft\n  'li_fat_id', // LinkedIn\n  'sclid', // Snapchat\n  'rclid', // Reddit\n  'irclickid', // Impact\n  // Layers click_id (primary key of sdk_clicks)\n  'click_id'\n]);\n\n/**\n * Decode one `application/x-www-form-urlencoded` token from a URL query\n * component. This is the single decoding rule every Layers SDK implements\n * (Kotlin, Swift, Dart, C#, and here):\n *\n *   - `+`   -> space          (ad platforms emit `running+shoes` for \"running shoes\")\n *   - `%XX` -> the byte 0xXX  (then UTF-8)\n *   - one pass: neither output feeds the other, so `%2B` yields a literal `+`\n *\n * The order below is load-bearing. Percent-decoding FIRST and then swapping\n * `+` for a space turns `%2B` into a space — a double decode that silently\n * corrupts any campaign name containing a real plus sign. Swapping `+` for\n * its own escape on the still-encoded token makes that impossible.\n *\n * Applies to the QUERY component only. Path and fragment are RFC 3986, where\n * `+` is an ordinary character.\n *\n * Never throws: a malformed escape (e.g. `%ZZ`) yields the raw token.\n */\nfunction decodeFormUrlComponent(encoded: string): string {\n  try {\n    return decodeURIComponent(encoded.replace(/\\+/g, '%20'));\n  } catch {\n    return encoded;\n  }\n}\n\n/**\n * Parse a raw Play Install Referrer string into a map of recognized attribution\n * params. Accepts either a bare query string (`a=1&b=2`) or a full URL.\n * Returns an empty object on parse failure.\n */\nexport function parseInstallReferrerUrl(referrer: string): Record<string, string> {\n  const out: Record<string, string> = {};\n  if (!referrer) return out;\n  try {\n    let qs = referrer;\n    const qIdx = qs.indexOf('?');\n    if (qIdx >= 0) qs = qs.slice(qIdx + 1);\n    // Some referrers arrive URL-encoded (e.g. Play wraps the referrer).\n    // This unwraps the envelope, so it is a plain percent-decode\n    // (decodeURIComponent, NOT decodeFormUrlComponent): at this point the\n    // string is one encoded blob, and turning its `+` characters into spaces\n    // would corrupt the query string it is about to become.\n    if (!qs.includes('=') && qs.includes('%3D')) {\n      qs = decodeURIComponent(qs);\n    }\n    for (const pair of qs.split('&')) {\n      const eq = pair.indexOf('=');\n      if (eq >= 0) {\n        const key = decodeFormUrlComponent(pair.slice(0, eq));\n        const value = decodeFormUrlComponent(pair.slice(eq + 1));\n        if (value && INSTALL_REFERRER_ATTRIBUTION_PARAMS.has(key)) {\n          out[key] = value;\n        }\n      }\n    }\n  } catch {\n    // ignore overall parse failure\n  }\n  return out;\n}\n\n/**\n * Build the canonical `install_referrer` event properties.\n *\n * This is the same shape Kotlin's `InstallReferrerTracker.buildReferrerProperties`\n * and Unity's `InstallReferrerResult.ToEventProperties` produce: seven base\n * fields from `ReferrerDetails`, then every recognized attribution param parsed\n * out of the raw referrer merged on top.\n *\n * All seven base fields are always present, with the same defaults the Android\n * API hands back when a value is absent (`0`, `\"\"`, `false`) — an SDK that\n * omitted a key on one install and included it on the next would give the\n * server two shapes for one event, which is the drift this unification exists\n * to end. `schema/fixtures/install-referrer-event.json` pins it, and the Kotlin\n * contract test asserts against the same file.\n */\nexport function buildInstallReferrerProperties(\n  referrer: InstallReferrerData\n): Record<string, unknown> {\n  const props: Record<string, unknown> = {\n    referrer: referrer.referrerUrl,\n    referrer_click_timestamp: referrer.referrerClickTimestamp ?? 0,\n    install_begin_timestamp: referrer.installBeginTimestamp ?? 0,\n    referrer_click_timestamp_server: referrer.referrerClickTimestampServer ?? 0,\n    install_begin_timestamp_server: referrer.installBeginTimestampServer ?? 0,\n    install_version: referrer.installVersion ?? '',\n    google_play_instant: referrer.googlePlayInstant ?? false\n  };\n  // Parsed attribution params (UTM + every recognized click ID), merged on top\n  // exactly as Kotlin's `putAll(parsed)` does.\n  Object.assign(props, parseInstallReferrerUrl(referrer.referrerUrl));\n  return props;\n}\n\n/**\n * Fetch the Google Play install referrer on Android via NativeModules.\n * Requires the `com.android.installreferrer` library on the native side.\n * Returns null on iOS or when the native module is not available.\n */\nexport async function getInstallReferrer(): Promise<InstallReferrerData | null> {\n  try {\n    const { NativeModules, Platform } = require('react-native');\n    if (Platform.OS !== 'android') return null;\n    const mod = NativeModules.LayersInstallReferrer as\n      | { getInstallReferrer?(): Promise<InstallReferrerData | null> }\n      | undefined;\n    if (!mod?.getInstallReferrer) return null;\n    const data = await mod.getInstallReferrer();\n    if (!data || !data.referrerUrl) return null;\n    return data;\n  } catch {\n    return null;\n  }\n}\n\n// --- Facebook Click ID ($fbc) Formatter ---\n\n/**\n * Format a raw fbclid into the Meta Conversions API `$fbc` parameter format.\n *\n * The format is: `fb.{subdomainIndex}.{creationTime}.{fbclid}`\n * - `subdomainIndex`: always `1` for app SDKs\n * - `creationTime`: Unix timestamp in milliseconds when the fbclid was captured\n * - `fbclid`: the raw Facebook Click Identifier\n *\n * The timestamp is captured once at format time so that every event carries\n * the same `$fbc` value (the capture time, not the event time).\n *\n * @see https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\nexport function formatFbc(fbclid: string, timestampMs?: number): string {\n  const ts = timestampMs ?? Date.now();\n  return `fb.1.${ts}.${fbclid}`;\n}\n\n// --- Deep Links ---\n\nexport interface DeepLinkData {\n  url: string;\n  scheme: string;\n  host: string;\n  path: string;\n  queryParams: Record<string, string>;\n  timestamp: number;\n}\n\nexport function setupDeepLinkListener(onDeepLink: (data: DeepLinkData) => void): () => void {\n  try {\n    const { Linking } = require('react-native');\n\n    const handler = (event: { url: string }) => {\n      const parsed = parseDeepLink(event.url);\n      if (parsed) onDeepLink(parsed);\n    };\n\n    const subscription = Linking.addEventListener('url', handler);\n\n    // Check initial URL\n    void Linking.getInitialURL().then((url: string | null) => {\n      if (url) {\n        const parsed = parseDeepLink(url);\n        if (parsed) onDeepLink(parsed);\n      }\n    });\n\n    return () => {\n      if (subscription?.remove) {\n        subscription.remove();\n      }\n    };\n  } catch {\n    return () => {};\n  }\n}\n\nexport function parseDeepLink(url: string): DeepLinkData | null {\n  try {\n    const parsed = new URL(url);\n    const params: Record<string, string> = {};\n    parsed.searchParams.forEach((value, key) => {\n      params[key] = value;\n    });\n    return {\n      url,\n      scheme: parsed.protocol.replace(':', ''),\n      host: parsed.hostname,\n      path: parsed.pathname,\n      queryParams: params,\n      timestamp: Date.now()\n    };\n  } catch {\n    return null;\n  }\n}\n\n// --- Expo Router Integration ---\n\n// Module-level map of `previous_screen_name` per SDK instance. We use a\n// WeakMap so the entry is GC'd when the SDK instance is released. This is\n// preferable to `useRef` here because the existing hook test infrastructure\n// only mocks `useEffect` from React (not `useRef`); using a module-level\n// map keeps the hook compatible with that test shape and gives us the\n// same `(from, to)` payload as the React Navigation observer.\nconst _expoRouterPreviousPath = new WeakMap<LayersReactNative, string>();\n\n// Last `pathname`+params this SDK instance actually tracked. The effect now\n// re-runs when the sdk arrives (see the deps below), and without this an\n// unchanged route would be tracked twice on that run. Keyed per instance, so a\n// new SDK instance legitimately re-tracks the current route — a new instance is\n// a new session, and its landing screen belongs in it.\ninterface ExpoRouterTrackedView {\n  /** The resolved pathname of the last tracked view. */\n  resolvedPath: string;\n  /** The screen name passed to screen(): the pattern on the auto path, the pathname on the manual path. */\n  name: string;\n}\nconst _expoRouterLastTracked = new WeakMap<LayersReactNative, ExpoRouterTrackedView>();\n\nexport interface ExpoRouterTrackingOptions {\n  /**\n   * `'auto'` marks views recorded by an auto-capture integration (the Expo\n   * provider's router tracker). They are tracked like any screen view but\n   * are kept out of the SKAN engine, so turning auto-capture on cannot change\n   * an app's iOS conversion values. Defaults to `'manual'`.\n   */\n  source?: 'auto' | 'manual';\n  /**\n   * The resolved pathname (`/profile/lin`) when `usePathname` returns\n   * something else, such as the route pattern the Expo provider names screens\n   * by. It is the deduplication key, so the provider's auto-captured view and\n   * a manual hook call on the same route count once. Defaults to the\n   * `usePathname` value.\n   */\n  resolvedPath?: string;\n}\n\n/**\n * Hook that emits `screen_view` for every Expo Router pathname change.\n *\n * One view per route per SDK instance: the deduplication key is the resolved\n * pathname alone. A param-only change on the same route (a search box or\n * filter chips mirrored into the URL) is the same screen and is not\n * re-tracked, and the Expo provider's auto-captured view and a manual call on\n * the same route count once, whichever effect runs first.\n *\n * The hook also tracks the previous screen name so the emitted event carries\n * `previous_screen_name`, matching the React Navigation observer in\n * `useLayersNavigationTracking`. This makes funnel analysis trivial\n * server-side: every screen event has a self-contained pair `(from, to)`\n * — no need to join adjacent rows.\n */\nexport function useLayersExpoRouterTracking(\n  sdkInstance: LayersReactNative | null | undefined,\n  usePathname: () => string,\n  useGlobalSearchParams: () => Record<string, string | string[]>,\n  options: ExpoRouterTrackingOptions = {}\n): void {\n  const autoCaptured = options.source === 'auto';\n  // `null`/`undefined` is tolerated: the documented composition is\n  // `useLayersExpoRouterTracking(useLayers().sdk, …)`, and that sdk is null for\n  // the whole startup window, so throwing on it crashed the first render of\n  // every app that wired the hook the way the docs show. A genuinely wrong\n  // argument still throws — silence there would hide a real integration\n  // mistake.\n  if (sdkInstance != null && typeof sdkInstance.screen !== 'function') {\n    throw new TypeError(\n      'useLayersExpoRouterTracking: expected a LayersReactNative instance (or null before init).'\n    );\n  }\n\n  const { useEffect } = require('react');\n  const pathname = usePathname();\n  const params = useGlobalSearchParams();\n  // The deduplication key: the resolved pathname, whatever the screen is\n  // named. Params are deliberately absent from it, so a param-only change on\n  // the same route is the same screen.\n  const routeKey = options.resolvedPath ?? pathname;\n\n  useEffect(() => {\n    // No-op until the SDK arrives. The effect re-runs when it does (sdkInstance\n    // is in the deps), so the landing screen is still recorded.\n    if (sdkInstance == null) return;\n    if (pathname) {\n      // Guard against tracking the same route twice for one SDK instance —\n      // the sdk null→client transition re-runs this effect on an unchanged\n      // route, and the provider's tracker and a manual hook call both see the\n      // same route. Returning to a route after navigating away still tracks:\n      // the intervening route overwrote this key.\n      // Duplicate rule: same resolved path, and either a manual call (any name\n      // on that path is the same screen) or the same auto-captured pattern.\n      // An auto view whose pattern changed while the path stayed the same\n      // (route groups sharing a URL, `(auth)/login` vs `(main)/login`) is a\n      // new screen and still tracks.\n      const last = _expoRouterLastTracked.get(sdkInstance);\n      if (last && last.resolvedPath === routeKey && (!autoCaptured || last.name === pathname)) {\n        return;\n      }\n\n      const properties: Record<string, unknown> = {};\n      Object.entries(params).forEach(([key, value]) => {\n        properties[key] = Array.isArray(value) ? value.join(',') : value;\n      });\n      const previous = _expoRouterPreviousPath.get(sdkInstance);\n      if (previous && previous !== pathname) {\n        properties.previous_screen_name = previous;\n      }\n      if (autoCaptured && typeof sdkInstance._screenAutoCaptured === 'function') {\n        sdkInstance._screenAutoCaptured(pathname, properties);\n      } else {\n        void sdkInstance.screen(pathname, properties);\n      }\n      _expoRouterPreviousPath.set(sdkInstance, pathname);\n      _expoRouterLastTracked.set(sdkInstance, { resolvedPath: routeKey, name: pathname });\n    }\n    // `sdkInstance` belongs in the deps: with only [pathname, routeKey] the\n    // effect never re-ran when `useLayers().sdk` flipped null→client on the\n    // landing route, so the first screen of every session went unrecorded.\n    // `params` is read above and is deliberately not a dependency: a param\n    // change without a route change must not re-run this effect.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [sdkInstance, pathname, routeKey, autoCaptured]);\n}\n"],"mappings":"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"}