{"version":3,"file":"controlled-navigation-adapter-DSB6Yd96.cjs","sources":["../../lib/vowel/adapters/navigation/controlled-navigation-adapter.ts"],"sourcesContent":["/**\n * @fileoverview Controlled Navigation Adapter - Cross-tab navigation for traditional sites\n * \n * This file contains the `ControlledNavigationAdapter` class which enables voice-controlled\n * navigation across browser tabs using the BroadcastChannel API. This is designed for\n * traditional multi-page applications (Shopify, WordPress, etc.) where the voice agent\n * runs in one tab and controls navigation in another tab.\n * \n * Use Cases:\n * - Shopify stores with voice navigation\n * - WordPress sites with voice control\n * - Traditional multi-page applications\n * - Any site where voice agent and content are in separate tabs\n * \n * Architecture:\n * - Tab A (Voice Agent): Sends navigation commands via BroadcastChannel\n * - Tab B (Controlled): Receives commands and performs page navigation\n * \n * Key Features:\n * - Cross-tab communication via BroadcastChannel\n * - Full page navigation support\n * - Configurable channel names\n * - Route configuration support\n * \n * @module @vowel.to/client/adapters/navigation\n * @author vowel.to\n * @license Proprietary\n * \n * @example\n * ```ts\n * // Voice agent tab\n * import { ControlledNavigationAdapter } from '@vowel.to/client/adapters/navigation';\n * \n * const navigationAdapter = new ControlledNavigationAdapter({\n *   channelName: 'vowel-shopify-nav'\n * });\n * \n * // Controlled tab (receives navigation commands)\n * const channel = new BroadcastChannel('vowel-shopify-nav');\n * channel.onmessage = (event) => {\n *   if (event.data.type === 'navigate') {\n *     window.location.href = event.data.url;\n *   }\n * };\n * ```\n */\n\nimport type { NavigationAdapter, VowelRoute } from '../../types';\n\n/**\n * Configuration options for ControlledNavigationAdapter\n */\nexport interface ControlledNavigationAdapterOptions {\n  /**\n   * BroadcastChannel name for communication\n   * Default: 'vowel-navigation'\n   */\n  channelName?: string;\n\n  /**\n   * Optional: Array of routes\n   * Can also be provided via Vowel config\n   */\n  routes?: VowelRoute[];\n  \n  /**\n   * Optional: Callback to stop voice session (called when controlled tab requests stop)\n   */\n  onStopVoiceSession?: () => void;\n  \n  /**\n   * Optional: Callback to get current voice state (called when controlled tab requests it)\n   */\n  onRequestVoiceState?: () => any;\n}\n\n/**\n * Navigation command message\n */\ninterface NavigationMessage {\n  type: 'navigate';\n  path: string;\n  url: string;\n  timestamp: number;\n}\n\n/**\n * Controlled Navigation Adapter\n * \n * Handles cross-tab navigation for traditional sites with page reloads.\n * Perfect for Shopify, WordPress, and other server-rendered platforms.\n */\nexport class ControlledNavigationAdapter implements NavigationAdapter {\n  private channel: BroadcastChannel;\n  private channelName: string;\n  private routes: VowelRoute[] = [];\n  private currentPath: string = '';\n  private onStopVoiceSession?: () => void;\n  private onRequestVoiceState?: () => any;\n\n  constructor(options: ControlledNavigationAdapterOptions = {}) {\n    this.channelName = options.channelName || 'vowel-navigation';\n    this.channel = new BroadcastChannel(this.channelName);\n    this.routes = options.routes || [];\n    this.currentPath = window.location.pathname;\n    this.onStopVoiceSession = options.onStopVoiceSession;\n    this.onRequestVoiceState = options.onRequestVoiceState;\n\n    // Listen for messages from controlled tabs\n    this.channel.onmessage = (event: MessageEvent) => {\n      const message = event.data;\n      \n      if (message.type === 'init') {\n        console.log(`📨 [ControlledNavigationAdapter] Received init message from controlled tab`);\n        console.log(`   URL: ${message.url}`);\n        console.log(`   Detected via: ${message.detectedVia}`);\n        console.log(`   Timestamp: ${message.timestamp}`);\n        \n        // Send acknowledgment back to controlled tab\n        const response = {\n          type: 'config',\n          config: {\n            routes: this.routes,\n            channelName: this.channelName\n          },\n          timestamp: Date.now()\n        };\n        \n        this.channel.postMessage(response);\n        console.log(`📡 [ControlledNavigationAdapter] Sent config response to controlled tab`);\n        console.log(`   ✅ Handshake complete`);\n      }\n      \n      // Handle stop voice session request from controlled tab\n      if (message.type === 'stopVoiceSession') {\n        console.log(`🛑 [ControlledNavigationAdapter] Stop voice session request from controlled tab`);\n        console.log(`   ⏱️  Timestamp: ${message.timestamp}`);\n        \n        if (this.onStopVoiceSession) {\n          console.log(`   🎤 Stopping voice session...`);\n          this.onStopVoiceSession();\n        } else {\n          console.warn(`   ⚠️  No onStopVoiceSession callback registered`);\n        }\n      }\n      \n      // Handle request for current voice state from controlled tab\n      if (message.type === 'requestVoiceState') {\n        console.log(`🎤 [ControlledNavigationAdapter] Voice state request from controlled tab`);\n        \n        if (this.onRequestVoiceState) {\n          const currentState = this.onRequestVoiceState();\n          console.log(`   📡 Sending current voice state:`, currentState);\n          \n          // Broadcast the current state\n          this.channel.postMessage({\n            type: 'voiceStateUpdate',\n            state: currentState,\n            timestamp: Date.now()\n          });\n        } else {\n          console.warn(`   ⚠️  No onRequestVoiceState callback registered`);\n        }\n      }\n    };\n\n    // Signal that this adapter needs a content window opened synchronously on session start\n    // VowelAgent checks this flag to open a controlled tab when user clicks the microphone\n    if (typeof window !== 'undefined') {\n      (window as any).__vowelNeedsContentWindow = true;\n      console.log(`🧭 [ControlledNavigationAdapter] Set __vowelNeedsContentWindow flag`);\n      console.log(`   ℹ️  VowelAgent will open controlled tab on session start`);\n    }\n\n    console.log(`🧭 [ControlledNavigationAdapter] Initialized on channel: ${this.channelName}`);\n    console.log(`   Mode: Cross-tab navigation (controlled tab mode)`);\n    console.log(`   👂 Listening for init and stopVoiceSession messages from controlled tabs`);\n  }\n\n  /**\n   * Navigate to a path by sending command via BroadcastChannel\n   */\n  async navigate(path: string): Promise<void> {\n    console.log(`🧭 [ControlledNavigationAdapter] Broadcasting navigation command: ${path}`);\n    \n    // Construct full URL\n    const url = new URL(path, window.location.origin).href;\n\n    // Send navigation command\n    const message: NavigationMessage = {\n      type: 'navigate',\n      path,\n      url,\n      timestamp: Date.now()\n    };\n\n    this.channel.postMessage(message);\n    \n    // Update internal state\n    this.currentPath = path;\n    \n    console.log(`   ✅ Navigation command sent to channel: ${this.channelName}`);\n    console.log(`   URL: ${url}`);\n  }\n\n  /**\n   * Get current path\n   */\n  getCurrentPath(): string {\n    return this.currentPath;\n  }\n\n  /**\n   * Get available routes\n   */\n  async getRoutes(): Promise<VowelRoute[]> {\n    return this.routes;\n  }\n\n  /**\n   * Set routes (useful for dynamic route updates)\n   */\n  setRoutes(routes: VowelRoute[]): void {\n    this.routes = routes;\n    console.log(`🧭 [ControlledNavigationAdapter] Routes updated: ${routes.length} routes`);\n  }\n  \n  /**\n   * Broadcast voice session state to controlled tabs\n   * This allows the controlled tab's microphone button to mirror the main tab's state\n   */\n  broadcastVoiceState(state: {\n    isConnected: boolean;\n    isConnecting: boolean;\n    isUserSpeaking: boolean;\n    isAISpeaking: boolean;\n    isAIThinking: boolean;\n    isResuming: boolean;\n  }): void {\n    const message = {\n      type: 'voiceStateUpdate',\n      state,\n      timestamp: Date.now()\n    };\n    \n    this.channel.postMessage(message);\n    \n    console.log(`📡 [ControlledNavigationAdapter] Broadcasted voice state to controlled tabs:`, state);\n  }\n  \n  /**\n   * Set the callback for stop voice session requests from controlled tabs\n   */\n  setStopVoiceSessionCallback(callback: () => void): void {\n    this.onStopVoiceSession = callback;\n    console.log(`🎤 [ControlledNavigationAdapter] Stop voice session callback registered`);\n  }\n  \n  /**\n   * Set the callback for voice state requests from controlled tabs\n   */\n  setRequestVoiceStateCallback(callback: () => any): void {\n    this.onRequestVoiceState = callback;\n    console.log(`🎤 [ControlledNavigationAdapter] Request voice state callback registered`);\n  }\n\n  /**\n   * Cleanup\n   */\n  cleanup(): void {\n    this.channel.close();\n    console.log(`🧭 [ControlledNavigationAdapter] Channel closed: ${this.channelName}`);\n  }\n}\n\n"],"names":[],"mappings":"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"}